Guest User

Untitled

a guest
Dec 8th, 2025
24
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 29.93 KB | None | 0 0
  1. # -*- coding: utf-8 -*-
  2. """
  3. Rhino 8 Python Script: Optimized Shadow Vectorizer with Enhanced Solid Generation
  4. Now uses Rhino's built-in sun for easier sun direction control
  5. Handles complex geometry and ensures shadow solids are created for all shadow types
  6. """
  7. import rhinoscriptsyntax as rs
  8. import Rhino
  9. import scriptcontext as sc
  10. import Rhino.Geometry as rg
  11. import math
  12. import time
  13.  
  14. def OptimizedShadowVectorizer():
  15.     """
  16.    Main function with performance optimizations and enhanced solid generation
  17.    """
  18.     # Get user input
  19.     caster_ids = rs.GetObjects("Select objects to cast shadows",
  20.                                rs.filter.surface | rs.filter.polysurface | rs.filter.mesh,
  21.                                preselect=True)
  22.     if not caster_ids:
  23.         print("No shadow casting objects selected.")
  24.         return
  25.  
  26.     receiver_ids = rs.GetObjects("Select surfaces to receive shadows",
  27.                                  rs.filter.surface | rs.filter.polysurface | rs.filter.mesh,
  28.                                  preselect=True)
  29.     if not receiver_ids:
  30.         print("No receiving surfaces selected.")
  31.         return
  32.  
  33.     sun_vector = GetSunVector()
  34.     if not sun_vector:
  35.         return
  36.    
  37.     # Add quality option for complex geometry
  38.     quality = rs.GetString("Mesh quality (Draft for complex objects)", "Standard",
  39.                           ["Draft", "Standard", "High"])
  40.    
  41.     # Add corner preservation option
  42.     preserve_corners = rs.GetString("Preserve sharp corners?", "Yes", ["Yes", "No"])
  43.    
  44.     self_shadow = rs.GetString("Include self-shadowing? (May be slow for complex objects)",
  45.                                "No", ["Yes", "No"])
  46.    
  47.     # Add option for solid generation
  48.     create_solids = rs.GetString("Create shadow solids?", "Yes", ["Yes", "No"])
  49.    
  50.     # Performance monitoring
  51.     start_time = time.time()
  52.     rs.EnableRedraw(False)
  53.    
  54.     try:
  55.         print("\nPreparing geometry with {} quality...".format(quality))
  56.        
  57.         # Convert objects to meshes with adaptive quality
  58.         caster_data = []
  59.         for i, cid in enumerate(caster_ids):
  60.             print("  Converting object {}/{}...".format(i+1, len(caster_ids)))
  61.             mesh = ConvertToMeshOptimized(cid, quality)
  62.             if mesh:
  63.                 # Estimate complexity and warn user
  64.                 complexity = mesh.Faces.Count * mesh.TopologyEdges.Count
  65.                 if complexity > 1000000:
  66.                     print("    WARNING: Object has {} faces - consider using Draft quality".format(
  67.                         mesh.Faces.Count))
  68.                 caster_data.append((cid, mesh))
  69.                
  70.                 # Allow escape for very complex objects
  71.                 if time.time() - start_time > 5:
  72.                     rs.EnableRedraw(True)
  73.                     if not rs.GetString("Processing is taking long. Continue?", "Yes", ["Yes", "No"]) == "Yes":
  74.                         return
  75.                     rs.EnableRedraw(False)
  76.  
  77.         if not caster_data:
  78.             print("Error: Could not convert any casting objects to meshes.")
  79.             return
  80.  
  81.         # Prepare receivers
  82.         receiver_breps = [rs.coercebrep(rid) for rid in receiver_ids if rs.coercebrep(rid)]
  83.         if not receiver_breps:
  84.             print("Error: No valid receiver surfaces found.")
  85.             return
  86.  
  87.         # Process shadows with progress tracking
  88.         all_shadow_curves = []
  89.         external_shadow_groups = []  # Track external shadows separately for solids
  90.         inter_shadow_groups = []     # Track inter-object shadows separately
  91.         self_shadow_groups = []      # Track self shadows separately
  92.        
  93.         total_objects = len(caster_data)
  94.        
  95.         for i, (caster_id, caster_mesh) in enumerate(caster_data):
  96.             print("\nProcessing Object {} of {}".format(i + 1, total_objects))
  97.            
  98.             # Update viewport periodically for feedback
  99.             if i % 2 == 0:
  100.                 rs.Redraw()
  101.                 rs.Prompt("Processing shadows: {}/{}".format(i+1, total_objects))
  102.            
  103.             # Generate external shadows (always do this - it's usually fast)
  104.             if receiver_breps:
  105.                 print("  Generating external shadows...")
  106.                 external_shadows = GenerateOptimizedShadows(caster_mesh, receiver_breps,
  107.                                                            sun_vector, quality, preserve_corners)
  108.                 if external_shadows:
  109.                     all_shadow_curves.extend(external_shadows)
  110.                     external_shadow_groups.append(external_shadows)
  111.                     print("    -> Found {} curves".format(len(external_shadows)))
  112.            
  113.             # Inter-object shadows (skip for single objects)
  114.             if len(caster_data) > 1:
  115.                 print("  Generating inter-object shadows...")
  116.                 other_receivers = []
  117.                 for j, (other_id, other_mesh) in enumerate(caster_data):
  118.                     if i != j:
  119.                         other_brep = rg.Brep.CreateFromMesh(other_mesh, True)
  120.                         if other_brep:
  121.                             other_receivers.append(other_brep)
  122.                
  123.                 if other_receivers:
  124.                     inter_shadows = GenerateOptimizedShadows(caster_mesh, other_receivers,
  125.                                                             sun_vector, quality, preserve_corners)
  126.                     if inter_shadows:
  127.                         all_shadow_curves.extend(inter_shadows)
  128.                         inter_shadow_groups.append(inter_shadows)
  129.                         print("    -> Found {} curves".format(len(inter_shadows)))
  130.            
  131.             # Self-shadows only if requested (expensive for complex geometry)
  132.             if self_shadow == "Yes":
  133.                 print("  Analyzing self-shadows (this may take time)...")
  134.                 # Use simplified method for complex meshes
  135.                 if caster_mesh.Faces.Count > 5000:
  136.                     print("    Using simplified method for complex geometry...")
  137.                     self_shadows = GenerateSimplifiedSelfShadows(caster_id, caster_mesh, sun_vector)
  138.                 else:
  139.                     self_shadows = GenerateOptimizedSelfShadows(caster_id, caster_mesh, sun_vector)
  140.                
  141.                 if self_shadows:
  142.                     all_shadow_curves.extend(self_shadows)
  143.                     self_shadow_groups.append(self_shadows)
  144.                     print("    -> Found {} self-shadow curves".format(len(self_shadows)))
  145.  
  146.         # Final processing
  147.         if all_shadow_curves:
  148.             print("\nFinalizing {} shadow curves...".format(len(all_shadow_curves)))
  149.            
  150.             # Process curves based on quantity
  151.             if len(all_shadow_curves) > 100:
  152.                 print("  Large dataset detected - using fast processing...")
  153.                 final_curves = FastProcessShadowCurves(all_shadow_curves)
  154.             else:
  155.                 final_curves = ProcessShadowCurvesOptimized(all_shadow_curves)
  156.            
  157.             OrganizeOutput(final_curves, "Shadow_Outlines", (64, 64, 64))
  158.            
  159.             # Create shadow solids if requested
  160.             if create_solids == "Yes":
  161.                 print("\nCreating shadow solids...")
  162.                 all_shadow_surfaces = []
  163.                
  164.                 # Create surfaces from main shadow curves
  165.                 if len(final_curves) < 200:  # Increased threshold for solid creation
  166.                     shadow_surfaces = CreateEnhancedShadowSurfaces(final_curves)
  167.                     if shadow_surfaces:
  168.                         all_shadow_surfaces.extend(shadow_surfaces)
  169.                         print("  Created {} main shadow surfaces".format(len(shadow_surfaces)))
  170.                 else:
  171.                     # Process in batches for large curve sets
  172.                     batch_size = 50
  173.                     for batch_start in range(0, len(final_curves), batch_size):
  174.                         batch_end = min(batch_start + batch_size, len(final_curves))
  175.                         batch_curves = final_curves[batch_start:batch_end]
  176.                         batch_surfaces = CreateEnhancedShadowSurfaces(batch_curves)
  177.                         if batch_surfaces:
  178.                             all_shadow_surfaces.extend(batch_surfaces)
  179.                     print("  Created {} shadow surfaces in batches".format(len(all_shadow_surfaces)))
  180.                
  181.                 # Try to create surfaces from individual shadow groups if main process didn't yield results
  182.                 if not all_shadow_surfaces:
  183.                     print("  Attempting to create solids from shadow groups...")
  184.                    
  185.                     # Process external shadows
  186.                     for shadow_group in external_shadow_groups:
  187.                         surfaces = TryCreateSurfacesFromGroup(shadow_group)
  188.                         if surfaces:
  189.                             all_shadow_surfaces.extend(surfaces)
  190.                    
  191.                     # Process inter-object shadows
  192.                     for shadow_group in inter_shadow_groups:
  193.                         surfaces = TryCreateSurfacesFromGroup(shadow_group)
  194.                         if surfaces:
  195.                             all_shadow_surfaces.extend(surfaces)
  196.                    
  197.                     # Process self shadows
  198.                     for shadow_group in self_shadow_groups:
  199.                         surfaces = TryCreateSurfacesFromGroup(shadow_group)
  200.                         if surfaces:
  201.                             all_shadow_surfaces.extend(surfaces)
  202.                
  203.                 if all_shadow_surfaces:
  204.                     OrganizeOutput(all_shadow_surfaces, "Shadow_Solids", (128, 128, 128))
  205.                     print("Total shadow surfaces created: {}".format(len(all_shadow_surfaces)))
  206.                 else:
  207.                     print("No closed curves found for creating shadow solids.")
  208.            
  209.             elapsed = time.time() - start_time
  210.             print("\nCOMPLETE in {:.1f} seconds: {} final curves created".format(
  211.                 elapsed, len(final_curves)))
  212.         else:
  213.             print("\nNo shadows were created.")
  214.  
  215.     except Exception as e:
  216.         print("Error: {}".format(e))
  217.         import traceback
  218.         traceback.print_exc()
  219.     finally:
  220.         rs.EnableRedraw(True)
  221.         rs.Prompt("")
  222.  
  223. def ConvertToMeshOptimized(obj_id, quality="Standard"):
  224.     """
  225.    Converts objects to mesh with adaptive quality settings
  226.    Quality levels are calibrated for performance vs accuracy
  227.    """
  228.     if rs.IsMesh(obj_id):
  229.         mesh = rs.coercemesh(obj_id)
  230.     else:
  231.         brep = rs.coercebrep(obj_id)
  232.         if not brep:
  233.             return None
  234.        
  235.         # Get object bounding box to scale parameters appropriately
  236.         bbox = brep.GetBoundingBox(True)
  237.         obj_size = bbox.Diagonal.Length
  238.        
  239.         params = rg.MeshingParameters()
  240.        
  241.         if quality == "Draft":
  242.             # Fast settings for complex objects
  243.             params.Tolerance = sc.doc.ModelAbsoluteTolerance * 5
  244.             params.MaximumEdgeLength = obj_size * 0.1  # Adaptive to object size
  245.             params.MinimumEdgeLength = obj_size * 0.01
  246.             params.GridAspectRatio = 0
  247.             params.RefineGrid = False
  248.             params.SimplePlanes = True
  249.             params.GridAngle = math.radians(30)  # Coarser angle
  250.            
  251.         elif quality == "High":
  252.             # High quality for final output
  253.             params.Tolerance = sc.doc.ModelAbsoluteTolerance * 0.5
  254.             params.MaximumEdgeLength = obj_size * 0.02
  255.             params.MinimumEdgeLength = sc.doc.ModelAbsoluteTolerance
  256.             params.GridAspectRatio = 0
  257.             params.RefineGrid = True
  258.             params.SimplePlanes = False
  259.             params.GridAngle = math.radians(15)
  260.            
  261.         else:  # Standard
  262.             # Balanced settings
  263.             params.Tolerance = sc.doc.ModelAbsoluteTolerance
  264.             params.MaximumEdgeLength = obj_size * 0.05
  265.             params.MinimumEdgeLength = obj_size * 0.005
  266.             params.GridAspectRatio = 0
  267.             params.RefineGrid = True
  268.             params.SimplePlanes = False
  269.             params.GridAngle = math.radians(20)
  270.        
  271.         meshes = rg.Mesh.CreateFromBrep(brep, params)
  272.         if not meshes:
  273.             return None
  274.        
  275.         mesh = rg.Mesh()
  276.         for m in meshes:
  277.             if m:
  278.                 mesh.Append(m)
  279.    
  280.     # Optimize mesh
  281.     mesh.Compact()
  282.     mesh.Weld(math.radians(22.5))
  283.     mesh.Normals.ComputeNormals()
  284.     mesh.FaceNormals.ComputeFaceNormals()
  285.     mesh.UnifyNormals()
  286.    
  287.     # Reduce if still too complex
  288.     if quality == "Draft" and mesh.Faces.Count > 10000:
  289.         mesh.Reduce(10000, False, 10, False)
  290.         print("    Reduced mesh to {} faces for performance".format(mesh.Faces.Count))
  291.    
  292.     return mesh
  293.  
  294. def GenerateOptimizedShadows(mesh, receiver_breps, sun_vector, quality, preserve_corners="Yes"):
  295.     """
  296.    Generates shadows with quality-based optimization and optional corner preservation
  297.    """
  298.     if not receiver_breps:
  299.         return []
  300.    
  301.     projected_ids = []
  302.    
  303.     # Get mesh silhouette
  304.     view_point = rg.Point3d.Origin - (sun_vector * 10000)
  305.     view_plane = rg.Plane(view_point, sun_vector)
  306.    
  307.     outline_polylines = mesh.GetOutlines(view_plane)
  308.     if not outline_polylines:
  309.         return []
  310.    
  311.     # Prepare curves for projection - with corner preservation option
  312.     curves_to_project = []
  313.     for polyline in outline_polylines:
  314.         if polyline and polyline.Count > 2:
  315.             # Convert to polyline curve to preserve sharp corners
  316.             temp_curve = rg.Polyline(list(polyline)).ToNurbsCurve()
  317.             if temp_curve:
  318.                 # Decision based on user preference and quality setting
  319.                 if preserve_corners == "No":
  320.                     # Original behavior - rebuild curves for smoothness
  321.                     if quality == "Draft":
  322.                         rebuild_pts = min(20, polyline.Count // 3)
  323.                     elif quality == "High":
  324.                         rebuild_pts = max(50, polyline.Count // 2)
  325.                     else:
  326.                         rebuild_pts = max(30, polyline.Count // 3)
  327.                    
  328.                     rebuilt_curve = temp_curve.Rebuild(rebuild_pts, 3, True)
  329.                     if rebuilt_curve:
  330.                         curves_to_project.append(rebuilt_curve)
  331.                     else:
  332.                         curves_to_project.append(temp_curve)
  333.                 else:
  334.                     # Preserve corners - only simplify if extremely complex
  335.                     if quality == "Draft" and polyline.Count > 100:
  336.                         # Very light simplification for performance
  337.                         rebuild_pts = max(50, polyline.Count // 2)
  338.                         rebuilt_curve = temp_curve.Rebuild(rebuild_pts, 3, True)
  339.                         if rebuilt_curve:
  340.                             curves_to_project.append(rebuilt_curve)
  341.                         else:
  342.                             curves_to_project.append(temp_curve)
  343.                     else:
  344.                         # Keep original curve to preserve all corners
  345.                         curves_to_project.append(temp_curve)
  346.    
  347.     if not curves_to_project:
  348.         return []
  349.    
  350.     # Project in batches for better memory management
  351.     batch_size = 50
  352.     for i in range(0, len(curves_to_project), batch_size):
  353.         batch = curves_to_project[i:i+batch_size]
  354.         try:
  355.             projected = rg.Curve.ProjectToBrep(
  356.                 batch, receiver_breps, sun_vector,
  357.                 sc.doc.ModelAbsoluteTolerance * (5 if quality == "Draft" else 1)
  358.             )
  359.             if projected:
  360.                 for proj_curve in projected:
  361.                     if (proj_curve and proj_curve.IsValid and
  362.                         proj_curve.GetLength() > sc.doc.ModelAbsoluteTolerance * 10):
  363.                         curve_id = sc.doc.Objects.AddCurve(proj_curve)
  364.                         if curve_id:
  365.                             projected_ids.append(curve_id)
  366.         except Exception as e:
  367.             print("    Warning: Batch projection failed: {}".format(e))
  368.             continue
  369.    
  370.     return projected_ids
  371.  
  372. def GenerateSimplifiedSelfShadows(obj_id, mesh, sun_vector):
  373.     """
  374.    Simplified self-shadow method for complex geometry
  375.    Uses sampling instead of checking every edge
  376.    """
  377.     shadow_curves = []
  378.    
  379.     # Create brep for projection target
  380.     mesh_brep = rg.Brep.CreateFromMesh(mesh, True) if rs.IsMesh(obj_id) else rs.coercebrep(obj_id)
  381.     if not mesh_brep:
  382.         return []
  383.    
  384.     # Sample edges instead of checking all
  385.     edge_count = mesh.TopologyEdges.Count
  386.     sample_rate = max(1, edge_count // 500)  # Check at most 500 edges
  387.    
  388.     curves_to_project = []
  389.    
  390.     for edge_idx in range(0, edge_count, sample_rate):
  391.         try:
  392.             face_indices = mesh.TopologyEdges.GetConnectedFaces(edge_idx)
  393.             if len(face_indices) == 2:
  394.                 f1_normal = rg.Vector3d(mesh.FaceNormals[face_indices[0]])
  395.                 f2_normal = rg.Vector3d(mesh.FaceNormals[face_indices[1]])
  396.                 dot1 = f1_normal * sun_vector
  397.                 dot2 = f2_normal * sun_vector
  398.                
  399.                 # Check if this is a terminator edge
  400.                 if (dot1 > 0.1 and dot2 <= -0.1) or (dot1 <= -0.1 and dot2 > 0.1):
  401.                     edge_curve = mesh.TopologyEdges.EdgeLine(edge_idx).ToNurbsCurve()
  402.                     if edge_curve:
  403.                         curves_to_project.append(edge_curve)
  404.                        
  405.                         # Limit number of curves to project
  406.                         if len(curves_to_project) >= 100:
  407.                             break
  408.         except Exception:
  409.             continue
  410.    
  411.     if not curves_to_project:
  412.         return []
  413.    
  414.     # Project and filter
  415.     try:
  416.         projected = rg.Curve.ProjectToBrep(
  417.             curves_to_project, [mesh_brep], sun_vector,
  418.             sc.doc.ModelAbsoluteTolerance * 2
  419.         )
  420.        
  421.         if projected:
  422.             for proj_curve in projected:
  423.                 if proj_curve and proj_curve.IsValid:
  424.                     # Quick distance check
  425.                     if proj_curve.GetLength() > sc.doc.ModelAbsoluteTolerance * 20:
  426.                         curve_id = sc.doc.Objects.AddCurve(proj_curve)
  427.                         if curve_id:
  428.                             shadow_curves.append(curve_id)
  429.     except Exception as e:
  430.         print("    Self-shadow projection error: {}".format(e))
  431.    
  432.     return shadow_curves
  433.  
  434. def GenerateOptimizedSelfShadows(obj_id, mesh, sun_vector):
  435.     """
  436.    Standard self-shadow generation with performance improvements
  437.    """
  438.     shadow_curves = []
  439.     mesh_brep = rg.Brep.CreateFromMesh(mesh, True) if rs.IsMesh(obj_id) else rs.coercebrep(obj_id)
  440.     if not mesh_brep:
  441.         return []
  442.    
  443.     curves_to_project = []
  444.     if mesh.FaceNormals.Count == 0:
  445.         mesh.FaceNormals.ComputeFaceNormals()
  446.    
  447.     # Pre-compute face visibility to sun
  448.     face_visibility = []
  449.     for i in range(mesh.FaceNormals.Count):
  450.         dot = rg.Vector3d(mesh.FaceNormals[i]) * sun_vector
  451.         face_visibility.append(dot > 0)
  452.    
  453.     # Only check edges between visible and non-visible faces
  454.     for edge_idx in range(mesh.TopologyEdges.Count):
  455.         try:
  456.             face_indices = mesh.TopologyEdges.GetConnectedFaces(edge_idx)
  457.             if len(face_indices) == 2:
  458.                 if face_visibility[face_indices[0]] != face_visibility[face_indices[1]]:
  459.                     edge_curve = mesh.TopologyEdges.EdgeLine(edge_idx).ToNurbsCurve()
  460.                     if edge_curve:
  461.                         curves_to_project.append(edge_curve)
  462.         except Exception:
  463.             continue
  464.    
  465.     if not curves_to_project:
  466.         return []
  467.    
  468.     # Project in smaller batches
  469.     batch_size = 20
  470.     for i in range(0, len(curves_to_project), batch_size):
  471.         batch = curves_to_project[i:i+batch_size]
  472.         try:
  473.             projected = rg.Curve.ProjectToBrep(
  474.                 batch, [mesh_brep], sun_vector, sc.doc.ModelAbsoluteTolerance
  475.             )
  476.            
  477.             if projected:
  478.                 for proj_curve in projected:
  479.                     if not (proj_curve and proj_curve.IsValid):
  480.                         continue
  481.                    
  482.                     # Find corresponding original curve
  483.                     proj_mid = proj_curve.PointAt(proj_curve.Domain.Mid)
  484.                     min_dist = float('inf')
  485.                     original_curve = None
  486.                    
  487.                     for crv in batch:
  488.                         dist = proj_mid.DistanceTo(crv.PointAt(crv.Domain.Mid))
  489.                         if dist < min_dist:
  490.                             min_dist = dist
  491.                             original_curve = crv
  492.                    
  493.                     if original_curve:
  494.                         dist = proj_curve.PointAtStart.DistanceTo(original_curve.PointAtStart)
  495.                         if dist > sc.doc.ModelAbsoluteTolerance * 5:
  496.                             curve_id = sc.doc.Objects.AddCurve(proj_curve)
  497.                             if curve_id:
  498.                                 shadow_curves.append(curve_id)
  499.         except Exception:
  500.             continue
  501.    
  502.     return shadow_curves
  503.  
  504. def FastProcessShadowCurves(curve_ids):
  505.     """
  506.    Fast processing for large numbers of curves
  507.    """
  508.     if not curve_ids:
  509.         return []
  510.    
  511.     # Simple join without extensive cleanup
  512.     tolerance = sc.doc.ModelAbsoluteTolerance * 10
  513.     joined = rs.JoinCurves(curve_ids, delete_input=True, tolerance=tolerance)
  514.    
  515.     if not joined:
  516.         return curve_ids
  517.    
  518.     # Quick filter by length
  519.     min_length = sc.doc.ModelAbsoluteTolerance * 50
  520.     valid_curves = []
  521.     for cid in joined:
  522.         if rs.IsCurve(cid) and rs.CurveLength(cid) > min_length:
  523.             valid_curves.append(cid)
  524.         else:
  525.             rs.DeleteObject(cid)
  526.    
  527.     return valid_curves
  528.  
  529. def ProcessShadowCurvesOptimized(curve_ids):
  530.     """
  531.    Standard processing with optimization
  532.    """
  533.     if not curve_ids:
  534.         return []
  535.    
  536.     # Remove duplicates first
  537.     unique_curves = FilterQuickDuplicates(curve_ids)
  538.    
  539.     # Join curves
  540.     joined = rs.JoinCurves(unique_curves, delete_input=True,
  541.                           tolerance=sc.doc.ModelAbsoluteTolerance*5)
  542.     valid_curves = joined if joined else unique_curves
  543.    
  544.     # Filter by length
  545.     min_length = sc.doc.ModelAbsoluteTolerance * 20
  546.     final_curves = []
  547.     for cid in valid_curves:
  548.         if rs.IsCurve(cid) and rs.CurveLength(cid) > min_length:
  549.             final_curves.append(cid)
  550.         else:
  551.             rs.DeleteObject(cid)
  552.    
  553.     return final_curves
  554.  
  555. def FilterQuickDuplicates(curve_ids):
  556.     """
  557.    Quick duplicate removal using spatial hashing
  558.    """
  559.     if len(curve_ids) < 2:
  560.         return curve_ids
  561.    
  562.     tolerance = sc.doc.ModelAbsoluteTolerance * 5
  563.     unique = []
  564.     midpoints_seen = set()
  565.    
  566.     for cid in curve_ids:
  567.         curve = rs.coercecurve(cid)
  568.         if curve:
  569.             mid = curve.PointAtNormalizedLength(0.5)
  570.             # Create spatial hash
  571.             hash_key = (
  572.                 round(mid.X / tolerance),
  573.                 round(mid.Y / tolerance),
  574.                 round(mid.Z / tolerance),
  575.                 round(curve.GetLength() / tolerance)
  576.             )
  577.            
  578.             if hash_key not in midpoints_seen:
  579.                 midpoints_seen.add(hash_key)
  580.                 unique.append(cid)
  581.             else:
  582.                 rs.DeleteObject(cid)
  583.    
  584.     return unique
  585.  
  586. def CreateEnhancedShadowSurfaces(curve_ids):
  587.     """
  588.    Enhanced version that creates planar surfaces from closed shadow curves
  589.    with better handling of complex cases
  590.    """
  591.     if not curve_ids:
  592.         return []
  593.    
  594.     surfaces = []
  595.    
  596.     # Separate closed and open curves
  597.     closed_curves = []
  598.     open_curves = []
  599.    
  600.     for cid in curve_ids:
  601.         if rs.IsCurveClosed(cid):
  602.             if rs.IsCurvePlanar(cid):
  603.                 closed_curves.append(cid)
  604.         else:
  605.             open_curves.append(cid)
  606.    
  607.     # Try to close open curves if they're nearly closed
  608.     for open_curve_id in open_curves:
  609.         curve = rs.coercecurve(open_curve_id)
  610.         if curve:
  611.             gap = curve.PointAtStart.DistanceTo(curve.PointAtEnd)
  612.             if gap < sc.doc.ModelAbsoluteTolerance * 50:
  613.                 # Try to close the curve
  614.                 line = rg.Line(curve.PointAtEnd, curve.PointAtStart)
  615.                 closing_segment = line.ToNurbsCurve()
  616.                 joined = rg.Curve.JoinCurves([curve, closing_segment],
  617.                                             sc.doc.ModelAbsoluteTolerance * 10)
  618.                 if joined and len(joined) > 0:
  619.                     closed_id = sc.doc.Objects.AddCurve(joined[0])
  620.                     if closed_id and rs.IsCurveClosed(closed_id):
  621.                         closed_curves.append(closed_id)
  622.    
  623.     if not closed_curves:
  624.         return []
  625.    
  626.     # Try different methods to create surfaces
  627.    
  628.     # Method 1: Boolean union and then create surfaces
  629.     try:
  630.         booleaned = rs.CurveBooleanUnion(closed_curves)
  631.         if booleaned:
  632.             for curve_id in booleaned:
  633.                 if rs.IsCurveClosed(curve_id) and rs.IsCurvePlanar(curve_id):
  634.                     srf = rs.AddPlanarSrf(curve_id)
  635.                     if srf:
  636.                         if isinstance(srf, list):
  637.                             surfaces.extend(srf)
  638.                         else:
  639.                             surfaces.append(srf)
  640.     except Exception as e:
  641.         print("    Boolean union method failed: {}".format(e))
  642.    
  643.     # Method 2: If method 1 didn't work, try direct surface creation
  644.     if not surfaces:
  645.         for curve_id in closed_curves:
  646.             try:
  647.                 if rs.IsCurveClosed(curve_id) and rs.IsCurvePlanar(curve_id):
  648.                     srf = rs.AddPlanarSrf(curve_id)
  649.                     if srf:
  650.                         if isinstance(srf, list):
  651.                             surfaces.extend(srf)
  652.                         else:
  653.                             surfaces.append(srf)
  654.             except Exception:
  655.                 continue
  656.    
  657.     return surfaces
  658.  
  659. def TryCreateSurfacesFromGroup(curve_ids):
  660.     """
  661.    Attempts to create surfaces from a group of shadow curves
  662.    """
  663.     if not curve_ids:
  664.         return []
  665.    
  666.     surfaces = []
  667.    
  668.     # First try to join curves in the group
  669.     joined = rs.JoinCurves(curve_ids, delete_input=False,
  670.                           tolerance=sc.doc.ModelAbsoluteTolerance * 10)
  671.    
  672.     if joined:
  673.         for curve_id in joined:
  674.             if rs.IsCurveClosed(curve_id) and rs.IsCurvePlanar(curve_id):
  675.                 try:
  676.                     srf = rs.AddPlanarSrf(curve_id)
  677.                     if srf:
  678.                         if isinstance(srf, list):
  679.                             surfaces.extend(srf)
  680.                         else:
  681.                             surfaces.append(srf)
  682.                 except Exception:
  683.                     pass
  684.    
  685.     return surfaces
  686.  
  687. def GetSunVector():
  688.     """
  689.    Gets sun direction vector from Rhino's built-in sun settings
  690.    Falls back to manual input if sun is not enabled
  691.    """
  692.     # Try to get Rhino's sun direction
  693.     sun = sc.doc.Lights.Sun
  694.    
  695.     if sun.Enabled:
  696.         # Get the sun vector (direction light travels)
  697.         sun_vector = sun.Vector
  698.         print("Using Rhino sun settings:")
  699.         print("  Azimuth: {:.1f}°".format(sun.Azimuth))
  700.         print("  Altitude: {:.1f}°".format(sun.Altitude))
  701.        
  702.         # Verify it's a valid vector
  703.         if sun_vector and sun_vector.Length > 0:
  704.             sun_vector.Unitize()
  705.             return sun_vector
  706.         else:
  707.             print("Warning: Sun vector is invalid, using fallback method")
  708.     else:
  709.         print("Rhino sun is not enabled. Please enable it in the Sun panel,")
  710.         print("or choose a manual method.")
  711.    
  712.     # Fallback to manual input
  713.     choice = rs.GetString("Sun direction method", "Default",
  714.                           ["Manual", "Default", "Vertical", "Angle", "EnableSun"])
  715.    
  716.     if choice == "EnableSun":
  717.         print("Please enable the sun in Rhino's Sun panel (Panels > Sun)")
  718.         print("Then run this script again.")
  719.         return None
  720.    
  721.     vec = None
  722.    
  723.     if choice == "Manual":
  724.         pt1 = rs.GetPoint("Click sun position (origin of ray)")
  725.         if not pt1:
  726.             return None
  727.         pt2 = rs.GetPoint("Click target point (defines direction)", base_point=pt1)
  728.         if not pt2:
  729.             return None
  730.         vec = pt2 - pt1
  731.     elif choice == "Vertical":
  732.         vec = rg.Vector3d(0, 0, -1)
  733.     elif choice == "Angle":
  734.         alt = rs.GetReal("Sun altitude (0-90 degrees)", 45, 0, 90)
  735.         azi = rs.GetReal("Sun azimuth (0-360, 0=N)", 135, 0, 360)
  736.         if alt is None or azi is None:
  737.             return None
  738.         alt_rad = math.radians(90 - alt)
  739.         azi_rad = math.radians(azi)
  740.         x = math.sin(alt_rad) * math.sin(azi_rad)
  741.         y = math.sin(alt_rad) * math.cos(azi_rad)
  742.         z = -math.cos(alt_rad)
  743.         vec = rg.Vector3d(x, y, z)
  744.     else:  # Default
  745.         vec = rg.Vector3d(1, 1, -1)
  746.    
  747.     if vec:
  748.         vec.Unitize()
  749.     return vec
  750.  
  751. def OrganizeOutput(object_ids, layer_name, layer_color):
  752.     """
  753.    Organizes objects onto designated layer
  754.    """
  755.     if not object_ids:
  756.         return
  757.     if not rs.IsLayer(layer_name):
  758.         rs.AddLayer(layer_name, layer_color)
  759.     rs.ObjectLayer(object_ids, layer_name)
  760.  
  761. # Main execution
  762. if __name__ == "__main__":
  763.     print("\n" + "="*50)
  764.     print(" OPTIMIZED SHADOW VECTORIZER WITH RHINO SUN")
  765.     print(" Uses Rhino's built-in sun for easy control")
  766.     print("="*50)
  767.     OptimizedShadowVectorizer()
  768.  
Advertisement
Add Comment
Please, Sign In to add comment