Guest User

Untitled

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