Hi,
I’m trying to figure out why the .PointLocation() method in the Alignment Class doesn’t return the intended values
( Help )
Is it because ref variables in parameters aren’t working the same way in Python ?
As of now; I only keep on having the values when I initiliaze the variables.
Here’s a paste of my code (Line 241) :
import clr
import time
import unicodedata # For normalizing special characters
# Add references to AutoCAD and Civil 3D assemblies
clr.AddReference('AcMgd')
clr.AddReference('AcCoreMgd')
clr.AddReference('AcDbMgd')
clr.AddReference('AecBaseMgd')
clr.AddReference('AecPropDataMgd')
clr.AddReference('AeccDbMgd')
# Import necessary namespaces
import Autodesk
from Autodesk.AutoCAD.ApplicationServices import *
from Autodesk.AutoCAD.DatabaseServices import *
from Autodesk.Civil.ApplicationServices import *
from Autodesk.Civil.DatabaseServices import *
from Autodesk.Aec.DatabaseServices import *
import math
import System.Reflection
# Input from Dynamo
doc = IN[0]
global easting_out
global northing_out
global bearing
# Function to get bearing in radians
def get_bearing(pipe):
start_point = pipe.StartPoint
end_point = pipe.EndPoint
dx = end_point.X - start_point.X
dy = end_point.Y - start_point.Y
return math.atan2(dy, dx)
# Function to convert radians to degrees
def rad_to_deg(radians):
return radians * 180 / math.pi
# Function to normalize angle to [0, 360)
def normalize_angle(angle_deg):
normalized = angle_deg - 360 * math.floor(angle_deg / 360.0)
return normalized if normalized >= 0 else normalized + 360 # Ensure positive result
# Function to inspect object properties
def list_properties(obj):
props = obj.GetType().GetProperties(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance)
return [prop.Name for prop in props]
# Function to normalize description for consistent matching
def normalize_description(desc):
if desc:
# Normalize Unicode characters (e.g., "É" to "E") and convert to uppercase
return unicodedata.normalize('NFKD', desc).encode('ASCII', 'ignore').decode('ASCII').upper()
return ""
# Function to determine if structure is connected at the beginning or end of a pipe
def is_connected_at_start(structure, pipe):
struct_loc = structure.Location
dist_start = ((pipe.StartPoint.X - struct_loc.X)**2 + (pipe.StartPoint.Y - struct_loc.Y)**2)**0.5
dist_end = ((pipe.EndPoint.X - struct_loc.X)**2 + (pipe.EndPoint.Y - struct_loc.Y)**2)**0.5
return dist_start < dist_end # True if closer to start, False if closer to end
# Function to extract the angle from COUDE description
def extract_coude_angle(desc):
try:
# Split the description (e.g., "COUDE 45") and get the angle part
angle_str = desc.split()[-1] # Last part should be the angle (e.g., "45")
return float(angle_str) # Convert to float (e.g., 45.0, 22.5)
except (IndexError, ValueError) as e:
# Default to 0 if parsing fails
return 0.0
# Main processing
output = []
try:
active_doc = Application.DocumentManager.MdiActiveDocument
db = active_doc.Database
civil_doc = CivilApplication.ActiveDocument
network_ids = civil_doc.GetPipeNetworkIds()
# Lock the document for writing
doc_lock = None
try:
doc_lock = active_doc.LockDocument()
output.append("Document locked for writing")
# Use a single transaction for all networks
with db.TransactionManager.StartTransaction() as t:
for network_id in network_ids:
try:
pipe_network = t.GetObject(network_id, OpenMode.ForRead)
structure_ids = pipe_network.GetStructureIds()
pipe_ids = pipe_network.GetPipeIds()
# Map structures to connected pipes
structure_to_pipes = {struct_id: [] for struct_id in structure_ids}
for pipe_id in pipe_ids:
pipe = t.GetObject(pipe_id, OpenMode.ForRead)
start_struct_id = pipe.StartStructureId
end_struct_id = pipe.EndStructureId
if not start_struct_id.IsNull and start_struct_id in structure_to_pipes:
structure_to_pipes[start_struct_id].append(pipe)
if not end_struct_id.IsNull and end_struct_id in structure_to_pipes:
structure_to_pipes[end_struct_id].append(pipe)
# Process each structure
for struct_id in structure_ids:
structure = t.GetObject(struct_id, OpenMode.ForRead)
output.append(f"Structure ID {struct_id} type: {structure.GetType().FullName}")
if not isinstance(structure, Autodesk.Civil.DatabaseServices.Structure):
output.append(f"Object with ID {struct_id} is not a Structure")
continue
output.append(f"Properties of Structure ID {struct_id}: {list_properties(structure)}")
raw_desc = structure.Description if structure.Description else ""
desc = normalize_description(raw_desc) # Normalize and uppercase the description
output.append(f"Structure {struct_id} raw description: {raw_desc}") # Log raw description
output.append(f"Structure {struct_id} normalized description: {desc}") # Log normalized description
connected_pipes = structure_to_pipes.get(struct_id, [])
pipe_count = len(connected_pipes)
output.append(f"Structure {struct_id} has {pipe_count} connected pipes")
bearings = []
if pipe_count > 0:
for pipe in connected_pipes:
bearing = get_bearing(pipe)
bearings.append(bearing)
output.append(f"Pipe bearing for Structure {struct_id}: {rad_to_deg(bearing)} degrees")
else:
output.append(f"Structure {struct_id}, Warning: No connected pipes, rotation will be set to 0° if applicable")
new_rotation = None
if desc == "VANNE" and pipe_count == 2 and abs(bearings[0] - bearings[1]) < 0.01:
new_rotation = bearings[0]
elif desc == "VANNE" and pipe_count != 2:
new_rotation = 0.0 # Default rotation if pipe count is not 2
output.append(f"Structure {struct_id}, VANNE has {pipe_count} pipes (expected 2), setting rotation to 0°")
elif desc == "T" and pipe_count == 3:
if abs(bearings[0] - bearings[1]) < 0.01 and abs(bearings[0] - bearings[2]) > 0.01:
new_rotation = bearings[2]
elif abs(bearings[1] - bearings[2]) < 0.01 and abs(bearings[0] - bearings[1]) > 0.01:
new_rotation = bearings[0]
elif abs(bearings[0] - bearings[2]) < 0.01 and abs(bearings[1] - bearings[0]) > 0.01:
new_rotation = bearings[1]
elif desc == "T" and pipe_count != 3:
new_rotation = 0.0 # Default rotation if pipe count is not 3
output.append(f"Structure {struct_id}, T has {pipe_count} pipes (expected 3), setting rotation to 0°")
elif desc in ["BOUCHON", "REDUIT", "MANCHON"] and pipe_count in [1, 2]:
if pipe_count == 1:
pipe = connected_pipes[0]
is_start = is_connected_at_start(structure, pipe)
bearing = bearings[0]
if desc == "BOUCHON":
new_rotation = bearing if is_start else bearing - math.pi # -180° if at end
output.append(f"Structure {struct_id}, BOUCHON connected at {'start' if is_start else 'end'}, setting rotation to {'bearing' if is_start else 'bearing - 180°':} {rad_to_deg(new_rotation)} degrees")
else:
new_rotation = bearing # Default for REDUIT/MANCHON
elif abs(bearings[0] - bearings[1]) < 0.01:
new_rotation = bearings[0]
elif desc in ["BOUCHON", "REDUIT", "MANCHON"] and pipe_count not in [1, 2]:
new_rotation = 0.0 # Default rotation if pipe count is not 1 or 2
output.append(f"Structure {struct_id}, {desc} has {pipe_count} pipes (expected 1 or 2), setting rotation to 0°")
elif desc == "POTEAU INCENDIE" and pipe_count == 1:
new_rotation = bearings[0] # Align with the pipe bearing directly
output.append(f"Structure {struct_id}, POTEAU INCENDIE setting rotation to pipe bearing: {rad_to_deg(new_rotation)} degrees")
elif desc == "POTEAU INCENDIE" and pipe_count != 1:
new_rotation = 0.0 # Default rotation if pipe count is not 1
output.append(f"Structure {struct_id}, POTEAU INCENDIE has {pipe_count} pipes (expected 1), setting rotation to 0°")
elif desc in ["COUDE 11", "COUDE 22.5", "COUDE 45", "COUDE 90"] and pipe_count == 2:
pipe0, pipe1 = connected_pipes
struct_loc = structure.Location
dist_start0 = ((pipe0.StartPoint.X - struct_loc.X)**2 + (pipe0.StartPoint.Y - struct_loc.Y)**2)**0.5
dist_end0 = ((pipe0.EndPoint.X - struct_loc.X)**2 + (pipe0.EndPoint.Y - struct_loc.Y)**2)**0.5
pipe0_is_start = dist_start0 < dist_end0
dist_start1 = ((pipe1.StartPoint.X - struct_loc.X)**2 + (pipe1.StartPoint.Y - struct_loc.Y)**2)**0.5
dist_end1 = ((pipe1.EndPoint.X - struct_loc.X)**2 + (pipe1.EndPoint.Y - struct_loc.Y)**2)**0.5
pipe1_is_start = dist_start1 < dist_end1
incoming_bearing = bearings[0] if pipe0_is_start else bearings[1]
outgoing_bearing = bearings[1] if pipe0_is_start else bearings[0]
output.append(f"Structure {struct_id}, Incoming bearing (degrees): {rad_to_deg(incoming_bearing)}")
output.append(f"Structure {struct_id}, Outgoing bearing (degrees): {rad_to_deg(outgoing_bearing)}")
# Calculate the angle difference (normalized to [-180°, 180°])
angle_diff = normalize_angle(rad_to_deg(outgoing_bearing) - rad_to_deg(incoming_bearing))
if angle_diff > 180:
angle_diff -= 360 # Normalize to [-180°, 180°]
elif angle_diff < -180:
angle_diff += 360
# Check for near-straight pipes
coude_angle = extract_coude_angle(desc) # Extract angle (e.g., 11, 22.5, 45, 90)
if abs(angle_diff) < 1.0: # Near-straight (less than 1° difference)
new_rotation = incoming_bearing
output.append(f"Structure {struct_id}, {desc} pipes are near-straight (angle diff {angle_diff}°), setting rotation to incoming bearing: {rad_to_deg(new_rotation)} degrees")
else:
if angle_diff < 0: # Outgoing turns left (counterclockwise)
new_rotation = incoming_bearing + math.pi - math.radians(coude_angle) # +180° - COUDE angle
output.append(f"Structure {struct_id}, {desc} outgoing goes left, setting rotation to incoming + 180° - {coude_angle}°: {rad_to_deg(new_rotation)} degrees")
else: # Outgoing turns right (clockwise)
new_rotation = outgoing_bearing - math.radians(coude_angle)
output.append(f"Structure {struct_id}, {desc} outgoing goes right, setting rotation to outgoing - {coude_angle}°: {rad_to_deg(new_rotation)} degrees")
# Force a 1° change if rotation matches current, with guard clause
current_rotation = structure.Rotation
current_deg = normalize_angle(rad_to_deg(current_rotation))
new_deg = normalize_angle(rad_to_deg(new_rotation))
if abs(current_deg - new_deg) < 1.0: # Use degrees for comparison
new_rotation += math.radians(1) # Add 1 degree
output.append(f"Structure {struct_id}, Forcing rotation change by +1° due to match with current rotation")
elif desc in ["COUDE 11", "COUDE 22.5", "COUDE 45", "COUDE 90"] and pipe_count != 2:
new_rotation = 0.0 # Default rotation if pipe count is not 2
output.append(f"Structure {struct_id}, {desc} has {pipe_count} pipes (expected 2), setting rotation to 0°")
elif desc.upper().find("PUISARD") != -1:
output.append(f"Structure {struct_id} matched as PUISARD-type structure with description: {raw_desc}")
alignment_id = getattr(structure, 'RefAlignmentId', None) # Get the alignment ID
output.append(f"Structure {struct_id}, RefAlignmentId: {alignment_id}") # Debug alignment ID
if alignment_id and not alignment_id.IsNull:
alignment = t.GetObject(alignment_id, OpenMode.ForRead)
struct_loc = structure.Location # Get the structure's 3D point (X, Y, Z)
easting = struct_loc.X # Use X as Easting
northing = struct_loc.Y # Use Y as Northing
tolerance = 10000.0 # Tolerance in drawing units (e.g., 5 meters or 5 feet)
try:
# First, project the structure's coordinates onto the alignment to get station and offset
station = structure.Station
offset = structure.Offset
output.append(f"Structure {struct_id}, Projected Station: {station}, Offset: {offset}")
# Now use the station and offset to get the point and bearing
easting_out = 0.0
northing_out = 0.0
bearing = 0.0
# easting_out, northing_out, bearing = alignment.PointLocation(station, offset, tolerance, easting_out, northing_out, bearing)
# easting_out, northing_out, bearing = alignment.PointLocation(station, offset, tolerance)
alignment.PointLocation(station, offset, tolerance, easting_out, northing_out, bearing)
output.append(f"Structure {struct_id}, Computed Easting: {easting_out}, Northing: {northing_out}, Bearing: {rad_to_deg(bearing)} degrees")
new_rotation = bearing # Set rotation to the alignment's bearing (in radians)
except Exception as e:
output.append(f"Structure {struct_id}, Error using PointLocation : {str(e)}")
new_rotation = 0.0 # Fallback to 0° if projection fails
output.append(f"Structure {struct_id}, Failed to project onto alignment, setting rotation to 0°")
else:
new_rotation = 0.0 # Fallback to 0° if no alignment
output.append(f"Structure {struct_id}, No valid alignment found, setting rotation to 0°")
# Apply rotation if calculated
if new_rotation is not None:
current_rotation = structure.Rotation
output.append(f"Structure {struct_id}, Current Rotation (degrees): {rad_to_deg(current_rotation)}")
# Normalize the new rotation for logging
normalized_new_rotation_deg = normalize_angle(rad_to_deg(new_rotation))
output.append(f"Structure {struct_id}, Description: {desc}, Setting Rotation (degrees): {normalized_new_rotation_deg}")
try:
structure.UpgradeOpen()
output.append(f"Applying rotation to Structure {struct_id}, WriteEnabled: {structure.IsWriteEnabled}")
structure.Rotation = new_rotation # Explicitly set rotation
updated_rotation = structure.Rotation
output.append(f"Structure {struct_id}, Updated Rotation (degrees) after set: {rad_to_deg(updated_rotation)}")
# Attempt to force handle-grip update via PartData or BlockReference
if hasattr(structure, 'PartData'):
part_data = structure.PartData
output.append(f"Structure {struct_id}, PartData available: {part_data is not None}")
if hasattr(structure, 'BlockId') and not structure.BlockId.IsNull:
block_ref = t.GetObject(structure.BlockId, OpenMode.ForWrite)
output.append(f"Structure {struct_id}, BlockReference found, IsWriteEnabled: {block_ref.IsWriteEnabled}")
if block_ref and isinstance(block_ref, BlockReference):
output.append(f"Structure {struct_id} rotating BlockReference.Rotation to {rad_to_deg(new_rotation)}")
block_ref.Rotation = new_rotation
updated_block_rotation = block_ref.Rotation
output.append(f"Structure {struct_id}, BlockReference Updated Rotation (degrees): {rad_to_deg(updated_block_rotation)}")
else:
output.append(f"Structure {struct_id} has no valid BlockId, using Structure.Rotation only")
except Exception as upgrade_e:
output.append(f"Failed to upgrade or modify Structure {struct_id}: {str(upgrade_e)}")
raise
output.append(f"Finished processing network {network_id}")
active_doc.Editor.Regen()
active_doc.Editor.UpdateScreen()
time.sleep(1) # Give Civil 3D time to update between networks
except Exception as e:
output.append(f"Error for network {network_id}: {str(e)}")
raise
output.append("Committing transaction for all networks")
t.Commit()
active_doc.Editor.Regen()
active_doc.Editor.UpdateScreen()
time.sleep(1) # Final update
finally:
if doc_lock is not None:
doc_lock.Dispose()
output.append("Document lock disposed")
active_doc.Database.CloseInput(True)
active_doc.Editor.Regen()
active_doc.Editor.UpdateScreen()
time.sleep(1) # Additional delay for display update
except Exception as e:
output.append(f"Script failed: {str(e)}")
OUT = output