@Jorge_Jaramillo This is the code. Didn't want to burden the forum with a 1500 line script. But the operative code is the last 20-40 lines. If you really want to try running it, you load the plugin, select two halves of a miter joint and the inside edge.
#Author-Autodesk Inc.
#Description-Create matJoint
from mimetypes import init
import adsk.core, adsk.fusion, traceback
import math
from collections import namedtuple
from itertools import combinations
defaultMatJointName = 'MatJoint'
defaultShallowInsetLength = 1
defaultDeepInsetLength = 1
defaultBoardThickness = .75
defaultMillDiameter =.125
defaultFilletRadius = 1
defaultShallowInsetDepth = .25
defaultDeepInsetDepth = .5
defaultNumSplines = 1
# global set of event handlers to keep them referenced for the duration of the command
handlers = []
app = adsk.core.Application.get()
if app:
ui = app.userInterface
newComp = None
######## GEOMETRY HELPERS
class RelevantAnatomy:
def __init__(self, miterAngle, bodyA,miterFaceA,adjFaceA,edgeA, bodyB,miterFaceB,adjFaceB,edgeB,edgeOverlap):
self.miterAngle=miterAngle
self.bodyA=bodyA
self.miterFaceA=miterFaceA
self.adjFaceA=adjFaceA
self.edgeA=edgeA
self.bodyB=bodyB
self.miterFaceB=miterFaceB
self.adjFaceB=adjFaceB
self.edgeB=edgeB
self.edgeOverlap=edgeOverlap
def calculateRelevantAnatomyFromSelection(selectedEdge,selectedBodies):
app.log("calculateRelevantAnatomyFromSelection")
_edgeA = selectedEdge
_bodyA, _miterFaceA, _bodyB, _miterFaceB = validate_bodies_and_edge(_edgeA, selectedBodies)
if not all([_bodyA, _miterFaceA, _bodyB, _miterFaceB]):
app.log("Validation failed: One or more elements are missing.")
return None
# Find the colinear edge on faceB
_edgeB = find_colinear_edge_on_faceB(_edgeA, _miterFaceB)
if not _edgeB:
app.log("No colinear edge found on Face B.")
return None
# Find adjacent faces
_adjFaceA = find_adjacent_faces(_bodyA, _miterFaceA, _edgeA)
_adjFaceB = find_adjacent_faces(_bodyB, _miterFaceB, _edgeB)
if not _adjFaceA or not _adjFaceB:
app.log("Failed to find adjacent faces.")
return None
try:
_miterAngle = calculate_miter_angle(_miterFaceA,_adjFaceA,_edgeA)
except:
if ui:
ui.messageBox('miter exception?:\n{}'.format(traceback.format_exc()))
if not _miterAngle:
app.log("Failed to find miterAngle")
return None
_edgeOverlap = get_overlapping_segment(_edgeA,_edgeB)
if not _edgeOverlap:
app.log("edges don't overlap")
return None
app.log(f"geometryValid {_miterAngle}")
#selection = ui.activeSelections.add(_adjFaceA)
selection = ui.activeSelections.add(_miterFaceA)
result = RelevantAnatomy(
_miterAngle ,
_bodyA ,
_miterFaceA ,
_adjFaceA ,
_edgeA ,
_bodyB ,
_miterFaceB ,
_adjFaceB ,
_edgeB ,
_edgeOverlap
)
return result
def points_almost_equal(point1, point2, tolerance=1e-6):
"""Check if two points are approximately equal within a given tolerance.
Args:
point1 (adsk.core.Point3D): The first point.
point2 (adsk.core.Point3D): The second point.
tolerance (float): The tolerance within which the points are considered equal.
Returns:
bool: True if the points are approximately equal, False otherwise.
"""
return point1.distanceTo(point2) < tolerance
def str_vertex(pt3d):
# Assuming 'vertex' is an adsk.core.Point3D object
x = pt3d.x
y = pt3d.y
z = pt3d.z
return f"[{x:.2f}, {y:.2f}, {z:.2f}]"
def str_face(face):
# Initialize an empty list to hold vertex strings
vertices_str = []
# Iterate over the vertices of the face
for vertex in face.vertices:
# Convert each vertex to a string using str_vertex
vertex_str = str_vertex(vertex.geometry)
# Append the formatted string to the list
vertices_str.append(vertex_str)
# Join all vertex strings into a single string
return ' '.join(vertices_str)
def get_overlapping_segment(edge1, edge2):
# Assuming edge1 and edge2 are co-linear and defined by startVertex and endVertex
points = [
edge1.startVertex.geometry, edge1.endVertex.geometry,
edge2.startVertex.geometry, edge2.endVertex.geometry
]
points[0].idx = 0
points[1].idx = 1
points[2].idx = 2
points[3].idx = 3
# Sort the points based on their position along the line
# This simplistic sorting works if edges are perfectly aligned along an axis
# For more complex cases, sorting may require calculating positions along the line direction
points.sort(key=lambda point: (point.x, point.y, point.z))
# Check for overlap
if points_almost_equal(points[1],points[2]):
# There is no overlap, or the overlap is a single point
return None
else:
# Return the overlapping segment as a tuple of (startPoint, endPoint)
return (points[1], points[2])
def calculate_miter_angle(miterFaceA, adjFaceA, sharedEdge):
angleDegrees = 360-2*get_angle_between_faces(miterFaceA, adjFaceA,sharedEdge)
# Calculate the miter angle
miterAngle = angleDegrees
return miterAngle
def get_angle_between_faces(face1, face2, shared_edge):
try:
# Log the input face IDs (or other identifiable info)
app.log(f"Calculating angle between faces: {face1.tempId}, {face2.tempId}")
# Get the direction vector of the shared edge
edge_start = shared_edge.startVertex.geometry
edge_end = shared_edge.endVertex.geometry
edge_direction = adsk.core.Vector3D.create(edge_end.x - edge_start.x, edge_end.y - edge_start.y, edge_end.z - edge_start.z)
# Log the edge direction
# Get the normal vectors of the faces
normal1 = face1.geometry.normal
normal2 = face2.geometry.normal
# Log the normals
# Project the normals onto a plane perpendicular to the edge
normal1_projected = project_vector_onto_plane(normal1, edge_direction)
normal2_projected = project_vector_onto_plane(normal2, edge_direction)
# Log the projected normals
# Calculate the dot product
dot_product = normal1_projected.dotProduct(normal2_projected)
# Log the dot product
# Calculate the angle in radians and then convert to degrees
angle_radians = math.acos(dot_product)
angle_degrees = math.degrees(angle_radians)
# Log the final angle
return 180-angle_degrees
except:
if ui:
ui.messageBox('Failed:\n{}'.format(traceback.format_exc()))
def project_vector_onto_plane(vector, plane_normal):
# Normalize the plane normal for accurate calculations
plane_normal.normalize()
# Calculate the component of the vector parallel to the plane normal
parallel_component = plane_normal.copy()
parallel_component.scaleBy(vector.dotProduct(plane_normal))
# Subtract the parallel component from the original vector
# to get the component that lies in the plane
vector_projected = vector.copy()
vector_projected.subtract(parallel_component)
return vector_projected
def find_adjacent_faces(bodyA, faceA, edgeA):
app.log("find_adjacent_faces")
# Find the adjacent face on bodyA
adjacent_faceA = None
for face in bodyA.faces:
if face != faceA and edgeA in face.edges:
adjacent_faceA = face
break
return adjacent_faceA
def find_colinear_edge_on_faceB(selectedEdge, faceB):
app.log("find_colinear_edge_on_faceB")
for edge in faceB.edges:
if are_edges_colinear(selectedEdge, edge):
return edge
return None
def are_edges_colinear(edge1, edge2):
app.log("are_edges_colinear")
# Get the Line3D representations of the edges
line1 = edge1.geometry.asInfiniteLine()
line2 = edge2.geometry.asInfiniteLine()
# Check if the lines are colinear
return line1.isColinearTo(line2)
def calculate_combined_bounding_box(elements):
min_x = min_y = min_z = float('inf')
max_x = max_y = max_z = float('-inf')
for element in elements:
points = []
if isinstance(element, adsk.core.Point3D):
points = [element]
elif isinstance(element, adsk.fusion.SketchLine):
points = [element.startSketchPoint.geometry, element.endSketchPoint.geometry]
elif isinstance(element, adsk.fusion.SketchCircle):
center = element.centerSketchPoint.geometry
radius = element.radius
points = [
adsk.core.Point3D.create(center.x - radius, center.y - radius, center.z),
adsk.core.Point3D.create(center.x + radius, center.y + radius, center.z)
]
elif isinstance(element, adsk.fusion.SketchPoint):
points = [element.geometry]
elif isinstance(element, adsk.fusion.SketchLineList):
for line in element:
points.extend([line.startSketchPoint.geometry, line.endSketchPoint.geometry])
else:
app.log(f"Unhandled element type: {type(element)}")
continue
for point in points:
min_x = min(min_x, point.x)
min_y = min(min_y, point.y)
min_z = min(min_z, point.z)
max_x = max(max_x, point.x)
max_y = max(max_y, point.y)
max_z = max(max_z, point.z)
if min_x == float('inf') or max_x == float('-inf'):
app.log("No valid elements processed.")
return None
min_point = adsk.core.Point3D.create(min_x, min_y, min_z)
max_point = adsk.core.Point3D.create(max_x, max_y, max_z)
return adsk.core.BoundingBox3D.create(min_point, max_point)
#given edge and two bodies
#returns two bodies, associated faces of those bodies inside miter joint.
def validate_bodies_and_edge(selectedEdge, selectedBodies):
app.log("validate_bodies_and_edge")
bodyA, bodyB, faceA, faceB = None, None, None, None
# Check if the selected edge belongs to one of the bodies
for body in selectedBodies:
if selectedEdge in body.edges:
bodyA = body
break
if not bodyA:
app.log("Selected edge does not belong to any of the selected bodies.")
return None, None, None, None
# Identify bodyB as the other body
bodyB = selectedBodies[1] if bodyA == selectedBodies[0] else selectedBodies[0]
# Find a coplanar face on bodyB that overlaps with a face on bodyA
for face in bodyA.faces:
if selectedEdge in face.edges:
planeA = face.geometry
for otherFace in bodyB.faces:
planeB = otherFace.geometry
cop = planeA.isCoPlanarTo(planeB)
overlap = do_faces_overlap(face, otherFace)
app.log(f"comparing {face.tempId} to {otherFace.tempId} cop:{cop} overlap:{overlap}")
if cop:
app.log(f" face:{str_face(face)} otherface:{str_face(otherFace)}")
if cop and overlap:
faceA, faceB = face, otherFace
break
if faceA and faceB:
break
if not (faceA and faceB):
app.log("No corresponding coplanar and overlapping face found.")
return None, None, None, None
return bodyA, faceA, bodyB, faceB
def do_faces_overlap(faceA, faceB):
def calculate_midpoint(start, end):
return adsk.core.Point3D.create((start.x + end.x) / 2, (start.y + end.y) / 2, (start.z + end.z) / 2)
def create_check_vertex_list(faceA, faceB):
checkVertexList = []
# Add all vertices of faceA
for vertex in faceA.vertices:
checkVertexList.append(vertex.geometry)
# Add all vertices of faceB
for vertex in faceB.vertices:
checkVertexList.append(vertex.geometry)
# Add midpoints of each edge of faceA
for edge in faceA.edges:
start = edge.startVertex.geometry
end = edge.endVertex.geometry
checkVertexList.append(calculate_midpoint(start, end))
# Add midpoints of each edge of faceB
for edge in faceB.edges:
start = edge.startVertex.geometry
end = edge.endVertex.geometry
checkVertexList.append(calculate_midpoint(start, end))
return checkVertexList
def create_direction_vector(face):
# Create a direction vector on the plane of the face
normal = face.geometry.normal
return adsk.core.Vector3D.create(-normal.y, normal.x, 0) if normal.x != 0 or normal.y != 0 else adsk.core.Vector3D.create(0, -normal.z, normal.y)
def create_infinite_line(point, direction_vector):
# Fusion 360 may not support infinite lines directly, so return a tuple with the point and direction
return (point, direction_vector)
def calculate_line_edge_intersection(linePoint, lineDirection, edgeStart, edgeEnd):
# Create vectors
edgeDirection = adsk.core.Vector3D.create(edgeEnd.x - edgeStart.x, edgeEnd.y - edgeStart.y, edgeEnd.z - edgeStart.z)
lineToEdgeStart = adsk.core.Vector3D.create(edgeStart.x - linePoint.x, edgeStart.y - linePoint.y, edgeStart.z - linePoint.z)
# Cross products to find if line and edge are parallel
cross_prod = lineDirection.crossProduct(edgeDirection)
if cross_prod.length == 0: # The line and edge are parallel
return None
# Manually calculate the squared length of the cross product
denominator = cross_prod.x * cross_prod.x + cross_prod.y * cross_prod.y + cross_prod.z * cross_prod.z
if denominator == 0:
return None
t = (lineToEdgeStart.crossProduct(edgeDirection).dotProduct(cross_prod)) / denominator
u = (lineToEdgeStart.crossProduct(lineDirection).dotProduct(cross_prod)) / denominator
# Check if intersection point is within the edge segment and on the line
if 0 <= u <= 1:
intersection = adsk.core.Point3D.create(
linePoint.x + t * lineDirection.x,
linePoint.y + t * lineDirection.y,
linePoint.z + t * lineDirection.z
)
return intersection
return None
def project_point_on_line(point, linePoint, lineDirection):
# Create a vector from linePoint to the point
pointVector = adsk.core.Vector3D.create(point.x - linePoint.x, point.y - linePoint.y, point.z - linePoint.z)
# Project this vector onto the line direction (dot product)
projection = pointVector.dotProduct(lineDirection)
return projection
def do_segments_overlap(segmentA, segmentB):
A1, A2 = segmentA # Endpoints of segment A
B1, B2 = segmentB # Endpoints of segment B
# Convert the 3D problem into a 2D problem by dropping one of the coordinates
# Assuming the z-coordinate can be dropped (i.e., the segments are not vertical)
# This simplification works when segments are coplanar
A1_2d = (A1.x, A1.y)
A2_2d = (A2.x, A2.y)
B1_2d = (B1.x, B1.y)
B2_2d = (B2.x, B2.y)
return segments_intersect(A1_2d, A2_2d, B1_2d, B2_2d)
def any_segments_overlap(segmentsA, segmentsB):
for segmentA in segmentsA:
for segmentB in segmentsB:
if do_segments_overlap(segmentA, segmentB):
return True
return False
def segments_intersect(A1, A2, B1, B2):
def ccw(A, B, C):
return (C[1] - A[1]) * (B[0] - A[0]) > (B[1] - A[1]) * (C[0] - A[0])
def intersect(A, B, C, D):
return ccw(A, C, D) != ccw(B, C, D) and ccw(A, B, C) != ccw(A, B, D)
return intersect(A1, A2, B1, B2) or \
(point_on_segment(A1, B1, B2) or point_on_segment(A2, B1, B2) or \
point_on_segment(B1, A1, A2) or point_on_segment(B2, A1, A2))
def point_on_segment(P, A, B):
return min(A[0], B[0]) <= P[0] <= max(A[0], B[0]) and \
min(A[1], B[1]) <= P[1] <= max(A[1], B[1])
def get_line_segments(face, line):
linePoint, lineDirection = line
segments = []
for edge in face.edges:
edgeStart = edge.startVertex.geometry
edgeEnd = edge.endVertex.geometry
intersectionPoint = calculate_line_edge_intersection(linePoint, lineDirection, edgeStart, edgeEnd)
if intersectionPoint is not None:
segments.append(intersectionPoint)
# Sort segments along the line
segments.sort(key=lambda point: project_point_on_line(point, linePoint, lineDirection))
# Return pairs of points as segments
return [(segments[i], segments[i + 1]) for i in range(0, len(segments) - 1, 2)]
#list of vertices to create a cross section through
checkVertexList = create_check_vertex_list(faceA, faceB)
#the direction of that cross section
direction_vector = create_direction_vector(faceA) # Assuming faceA and faceB are coplanar
for point in checkVertexList:
line = create_infinite_line(point, direction_vector)
segmentsA = get_line_segments(faceA, line)
segmentsB = get_line_segments(faceB, line)
if any_segments_overlap(segmentsA, segmentsB):
return True
return False
def is_point_inside_polygon(point, polygon_vertices, plane):
# Project the point and polygon vertices onto the plane
projected_point = project_point_onto_plane(point, plane)
projected_polygon = [project_point_onto_plane(vertex.geometry, plane) for vertex in polygon_vertices]
# Check if the point is inside the polygon (2D check)
num_intersections = 0
for i in range(len(projected_polygon)):
p1 = projected_polygon[i]
p2 = projected_polygon[(i + 1) % len(projected_polygon)]
if (p1.y > projected_point.y) != (p2.y > projected_point.y):
if projected_point.x < (p2.x - p1.x) * (projected_point.y - p1.y) / (p2.y - p1.y) + p1.x:
num_intersections += 1
return num_intersections % 2 == 1 # Point is inside if the number of intersections is odd
def check_edge_intersection(edgeA, edgeB, plane):
# Project the start and end points of both edges onto the plane
startA = project_point_onto_plane(edgeA.startVertex.geometry, plane)
endA = project_point_onto_plane(edgeA.endVertex.geometry, plane)
startB = project_point_onto_plane(edgeB.startVertex.geometry, plane)
endB = project_point_onto_plane(edgeB.endVertex.geometry, plane)
# Check if the lines intersect
return lines_intersect(startA, endA, startB, endB)
def project_point_onto_plane(point, plane):
vectorToPoint = adsk.core.Vector3D.create(point.x - plane.origin.x, point.y - plane.origin.y, point.z - plane.origin.z)
distance = vectorToPoint.dotProduct(plane.normal)
return adsk.core.Point3D.create(point.x - distance * plane.normal.x, point.y - distance * plane.normal.y, point.z - distance * plane.normal.z)
def lines_intersect(p1, p2, p3, p4):
# Calculate intersection between two line segments
# This is a simplified version and may need refinement for edge cases
denom = ((p4.y - p3.y) * (p2.x - p1.x)) - ((p4.x - p3.x) * (p2.y - p1.y))
if denom == 0:
return False # Lines are parallel or coincident
uA = (((p4.x - p3.x) * (p1.y - p3.y)) - ((p4.y - p3.y) * (p1.x - p3.x))) / denom
uB = (((p2.x - p1.x) * (p1.y - p3.y)) - ((p2.y - p1.y) * (p1.x - p3.x))) / denom
if (0 <= uA <= 1) and (0 <= uB <= 1):
return True # Intersection point is within both line segments
return False
def process_face(face):
app = adsk.core.Application.get()
#_ui = app.userInterface
#app.log("process_face")
try:
# Check if the face object is valid
if face and face.isValid:
#app.log(f"Processing face with temp ID: {face.tempId}")
# Using the evaluator
evaluator = face.evaluator
# Check if evaluator is valid
if evaluator.isValid:
# Example usage of evaluator
# Get the area of the face
area = face.area
#app.log(f"Area of the face: {area} square cm")
# Get a point on the face
pointOnFace = face.pointOnFace
#app.log(f"Point on face: ({pointOnFace.x}, {pointOnFace.y}, {pointOnFace.z})")
else:
app.log("Invalid evaluator for the face.")
else:
app.log("Invalid or null BRepFace object provided.")
except Exception as e:
app.log(f"Error processing face: {str(e)}")
def is_point_inside_face(point, face):
process_face(face)
# Get the SurfaceEvaluator for the face
evaluator = face.evaluator
# Get the parameters (u, v) at the point on the surface
success, parameter = evaluator.getParameterAtPoint(point)
if not success:
return False
# Check if the parameters (u, v) are on the face
# Accessing u and v coordinates of the Point2D directly
u = parameter.x
v = parameter.y
point2D = adsk.core.Point2D.create(u, v)
isInside = evaluator.isParameterOnFace(point2D)
return isInside
def find_matching_vertex_in_edges(edges, point, tolerance=1e-6):
for edge in edges:
# Check start vertex
if edge.startVertex.geometry.isEqualTo(point):
return edge.startVertex
# Check end vertex
if edge.endVertex.geometry.isEqualTo(point):
return edge.endVertex
return None
###### HELP BUILD
def calculate_normalized_points(num_splines):
normalized_interval = 1 / num_splines
normalized_points = []
for i in range(num_splines):
point = (normalized_interval / 2) + (i * normalized_interval)
normalized_points.append(point)
return normalized_points
def interpolate_points(start_point, end_point, normalized_values):
interpolated_points = []
for value in normalized_values:
# Linear interpolation for each coordinate
x = start_point.x + (end_point.x - start_point.x) * value
y = start_point.y + (end_point.y - start_point.y) * value
z = start_point.z + (end_point.z - start_point.z) * value
# Create a new Point3D for the interpolated point
interpolated_point = adsk.core.Point3D.create(x, y, z)
interpolated_points.append(interpolated_point)
return interpolated_points
def create_sketch_and_project_points(face,normalized_points):
# Access the application and user interface
app = adsk.core.Application.get()
ui = app.userInterface
try:
# Get the product and design
product = app.activeProduct
design = adsk.fusion.Design.cast(product)
rootComp = design.rootComponent
# Create a new sketch on the specified face
sketches = rootComp.sketches
newSketch = sketches.add(face)
# Iterate through the normalized points and add them to the sketch
for point in normalized_points:
# Create a sketch point for each normalized point
newSketch.project(point)
return newSketch
except:
if ui:
ui.messageBox('Failed to create sketch and project points:\n{}'.format(traceback.format_exc()))
return None
def scale(x, in_min, in_max, out_min, out_max):
return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min
def create_parametric_point(app, sketch, vertexA, vertexB, percentage):
try:
# Project the vertices onto the sketch
projectedPointA = sketch.project(vertexA).item(0)
projectedPointB = sketch.project(vertexB).item(0)
# Create or get a user parameter for the percentage
design = app.activeProduct
param = design.userParameters.itemByName('Percentage')
if not param:
param = design.userParameters.add('Percentage', adsk.core.ValueInput.createByString(str(percentage)), '', 'Percentage along the line')
param.expression = str(percentage)
# Calculate the distance between the two points
distance = vertexA.geometry.distanceTo(vertexB.geometry)
# Add a dimension (driven by the parameter) between the two points
dimension = sketch.sketchDimensions.addDistanceDimension(
projectedPointA, projectedPointB,
adsk.fusion.DimensionOrientations.AlignedDimensionOrientation,
adsk.core.Point3D.create((projectedPointA.geometry.x + projectedPointB.geometry.x) / 2,
(projectedPointA.geometry.y + projectedPointB.geometry.y) / 2,
(projectedPointA.geometry.z + projectedPointB.geometry.z) / 2))
# Set the dimension to be driven by the parameter
dimension.parameter.expression = 'Percentage * ' + str(distance)
return projectedPointA, projectedPointB
except Exception as e:
if app.userInterface:
app.userInterface.messageBox('Failed:\n{}'.format(traceback.format_exc()))
return None, None
def create_parametric_point_on_line(app, fraction, lineStart, lineEnd):
try:
design = app.activeProduct
rootComp = design.rootComponent
# Create the main sketch on a plane that intersects the line
mainSketch = rootComp.sketches.add(rootComp.xYConstructionPlane)
# Project the start and end points onto the main sketch
projectedPointStart = mainSketch.project(lineStart).item(0)
projectedPointEnd = mainSketch.project(lineEnd).item(0)
# Create a line between the projected points
sketchLine = mainSketch.sketchCurves.sketchLines.addByTwoPoints(projectedPointStart, projectedPointEnd)
# Create a user parameter for the fraction if it doesn't exist
param = design.userParameters.itemByName('Fraction')
if not param:
param = design.userParameters.add('Fraction', adsk.core.ValueInput.createByString(str(fraction)), '', 'Fraction along the line')
param.expression = str(fraction)
# Add a sketch point at the start of the line
sketchPoint = mainSketch.sketchPoints.add(sketchLine.startSketchPoint.geometry)
# Add a dimension controlled by the fraction
dimension = mainSketch.sketchDimensions.addDistanceDimension(
sketchLine.startSketchPoint, sketchPoint,
adsk.fusion.DimensionOrientations.AlignedDimensionOrientation,
sketchPoint.geometry)
dimension.parameter.expression = 'Fraction * ' + sketchLine.length.expression
return sketchPoint
except Exception as e:
if app.userInterface:
app.userInterface.messageBox('Failed:\n{}'.format(traceback.format_exc()))
return None
def add_parametric_point_on_line(app, line, percentage):
try:
design = app.activeProduct
rootComp = design.rootComponent
# Create a new sketch on the same plane as the line
sketch = rootComp.sketches.add(line.parentSketch.referencePlane)
# Use the start and end points of the line
lineStart = line.startSketchPoint.geometry
lineEnd = line.endSketchPoint.geometry
# Add a sketch point at the start point of the line
sketchPoint = sketch.sketchPoints.add(lineStart)
# Constrain the point to the line
sketch.geometricConstraints.addCoincident(sketchPoint, line)
# Create or get a user parameter for the percentage
param = design.userParameters.itemByName('LinePercentage')
if not param:
param = design.userParameters.add('LinePercentage', adsk.core.ValueInput.createByString(str(percentage)), 'cm', 'Percentage along the line')
param.expression = str(percentage)
# Add a dimension driven by the parameter
dimension = sketch.sketchDimensions.addDistanceDimension(
line.startSketchPoint, sketchPoint,
adsk.fusion.DimensionOrientations.AlignedDimensionOrientation,
sketchPoint.geometry)
dimension.parameter.expression = 'LinePercentage * ' + line.length.expression
return sketchPoint
except Exception as e:
app.log(f"Error in add_parametric_point_on_line: {str(e)}")
if app.userInterface:
app.userInterface.messageBox('Failed:\n{}'.format(traceback.format_exc()))
return None
def add_point_at_midpoint_of_line(sketch, line):
try:
# Calculate the midpoint coordinates
startX = line.startSketchPoint.geometry.x
startY = line.startSketchPoint.geometry.y
endX = line.endSketchPoint.geometry.x
endY = line.endSketchPoint.geometry.y
midX = (startX + endX) / 2
midY = (startY + endY) / 2
# Create a point at the midpoint
midpoint = adsk.core.Point3D.create(midX, midY, 0)
sketchPoint = sketch.sketchPoints.add(midpoint)
# Add a midpoint constraint between the sketch point and the line
sketch.geometricConstraints.addMidPoint(sketchPoint, line)
return sketchPoint
except Exception as e:
if app.userInterface:
app.userInterface.messageBox('Failed to add point at midpoint:\n{}'.format(traceback.format_exc()))
return None
######## ADD IN SETUP AND GLUE PARAM DEFINITIONS
def createNewComponent():
# Get the active design.
product = app.activeProduct
design = adsk.fusion.Design.cast(product)
rootComp = design.rootComponent
allOccs = rootComp.occurrences
newOcc = allOccs.addNewComponent(adsk.core.Matrix3D.create())
return newOcc.component
class MatJointCommandExecuteHandler(adsk.core.CommandEventHandler):
def __init__(self):
super().__init__()
def notify(self, args):
try:
unitsMgr = app.activeProduct.unitsManager
command = args.firingEvent.sender
inputs = command.commandInputs
matJoint = MatJoint()
for input in inputs:
if input.id == 'selectBody':
matJoint._selectedBodies = []
for i in range(input.selectionCount):
selectedBody = input.selection(i).entity
matJoint._selectedBodies.append(selectedBody)
elif input.id == 'selectEdge' and input.selectionCount > 0:
matJoint._selectedEdge = None
matJoint._selectedEdge = input.selection(0).entity
elif input.id == 'matJointName':
matJoint.matJointName = input.value
elif input.id == 'shallowInsetLength':
matJoint.shallowInsetLength = unitsMgr.evaluateExpression(input.expression, unitsMgr.defaultLengthUnits)
elif input.id == 'deepInsetLength':
matJoint.deepInsetLength = unitsMgr.evaluateExpression(input.expression, unitsMgr.defaultLengthUnits)
elif input.id == 'boardThickness':
matJoint.boardThickness = unitsMgr.evaluateExpression(input.expression, unitsMgr.defaultLengthUnits)
elif input.id == 'millDiameter':
matJoint.millDiameter = unitsMgr.evaluateExpression(input.expression, unitsMgr.defaultLengthUnits)
elif input.id == 'filletRadius':
matJoint.filletRadius = unitsMgr.evaluateExpression(input.expression, unitsMgr.defaultLengthUnits)
elif input.id == 'shallowInsetDepth':
matJoint.shallowInsetDepth =unitsMgr.evaluateExpression(input.expression, unitsMgr.defaultLengthUnits)
elif input.id == 'deepInsetDepth':
matJoint.deepInsetDepth = unitsMgr.evaluateExpression(input.expression, unitsMgr.defaultLengthUnits)
elif input.id == 'numSplines':
matJoint.numSplines = input.value
matJoint.buildMatJoint()
args.isValidResult = True
except:
if ui:
ui.messageBox('Failed:\n{}'.format(traceback.format_exc()))
class MatJointCommandDestroyHandler(adsk.core.CommandEventHandler):
def __init__(self):
super().__init__()
def notify(self, args):
try:
# when the command is done, terminate the script
# this will release all globals which will remove all event handlers
adsk.terminate()
except:
if ui:
ui.messageBox('Failed:\n{}'.format(traceback.format_exc()))
class MatJointCommandCreatedHandler(adsk.core.CommandCreatedEventHandler):
def __init__(self):
super().__init__()
def notify(self, args):
try:
cmd = args.command
cmd.isRepeatable = False
onExecute = MatJointCommandExecuteHandler()
cmd.execute.add(onExecute)
onExecutePreview = MatJointCommandExecuteHandler()
cmd.executePreview.add(onExecutePreview)
onDestroy = MatJointCommandDestroyHandler()
cmd.destroy.add(onDestroy)
# keep the handler referenced beyond this function
handlers.append(onExecute)
handlers.append(onExecutePreview)
handlers.append(onDestroy)
unitsMgr = app.activeProduct.unitsManager
#define the inputs
inputs = cmd.commandInputs
inputs.addStringValueInput('matJointName', 'MatJoint Name', defaultMatJointName)
bodySelectionInput = inputs.addSelectionInput('selectBody', 'Select Two Bodies', 'Select Two Bodies')
bodySelectionInput.addSelectionFilter('SolidBodies')
bodySelectionInput.setSelectionLimits(2, 2)
edgeSelectionInput = inputs.addSelectionInput('selectEdge', 'Select an Edge', 'Select a single edge')
edgeSelectionInput.addSelectionFilter('LinearEdges') # Filter to select only linear edges
edgeSelectionInput.setSelectionLimits(1, 1) # Limit to a single selection
initShallowInsetLength = adsk.core.ValueInput.createByReal(defaultShallowInsetLength)
inputs.addValueInput('shallowInsetLength', 'Shallow Inset Length',unitsMgr.defaultLengthUnits, initShallowInsetLength)
initDeepInsetLength = adsk.core.ValueInput.createByReal(defaultDeepInsetLength)
inputs.addValueInput('deepInsetLength', 'Deep Inset Length',unitsMgr.defaultLengthUnits, initDeepInsetLength)
initBoardThickness = adsk.core.ValueInput.createByReal(defaultBoardThickness)
inputs.addValueInput('boardThickness', 'Board Thickness',unitsMgr.defaultLengthUnits, initBoardThickness)
initMillDiameter = adsk.core.ValueInput.createByReal(defaultMillDiameter)
inputs.addValueInput('millDiameter', 'mill Diameter',unitsMgr.defaultLengthUnits, initMillDiameter)
initFilletRadius = adsk.core.ValueInput.createByReal(defaultFilletRadius)
inputs.addValueInput('filletRadius', 'fillet Radius',unitsMgr.defaultLengthUnits, initFilletRadius)
initShallowInsetDepth = adsk.core.ValueInput.createByReal(defaultShallowInsetDepth)
inputs.addValueInput('shallowInsetDepth', 'shallow Inset Depth',unitsMgr.defaultLengthUnits, initShallowInsetDepth)
initDeepInsetDepth = adsk.core.ValueInput.createByReal(defaultDeepInsetDepth)
inputs.addValueInput('deepInsetDepth', 'Deep Inset Depth',unitsMgr.defaultLengthUnits, initDeepInsetDepth)
inputs.addIntegerSpinnerCommandInput('numSplines', 'Number of Splines',1 ,7,1, 1)
except:
if ui:
ui.messageBox('Failed:\n{}'.format(traceback.format_exc()))
def run(context):
try:
product = app.activeProduct
design = adsk.fusion.Design.cast(product)
if not design:
ui.messageBox('A Fusion design must be active when running this script.')
return
commandDefinitions = ui.commandDefinitions
#check the command exists or not
cmdDef = commandDefinitions.itemById('MatJoint')
if not cmdDef:
cmdDef = commandDefinitions.addButtonDefinition('MatJoint',
'Create MatJoint',
'Create a matJoint.',
'./resources') # relative resource file path is specified
onCommandCreated = MatJointCommandCreatedHandler()
cmdDef.commandCreated.add(onCommandCreated)
# keep the handler referenced beyond this function
handlers.append(onCommandCreated)
inputs = adsk.core.NamedValues.create()
cmdDef.execute(inputs)
# prevent this module from being terminate when the script returns, because we are waiting for event handlers to fire
adsk.autoTerminate(False)
except:
if ui:
ui.messageBox('Failed:\n{}'.format(traceback.format_exc()))
###### NUTS BOLTS CONSTRUCTION
class MatJoint:
def __init__(self):
self._matJointName = defaultMatJointName
self._shallowInsetLength = defaultShallowInsetLength
self._deepInsetLength=defaultDeepInsetLength
self._boardThickness=defaultBoardThickness
self._millDiameter=defaultMillDiameter
self._filletRadius=defaultFilletRadius
self._shallowInsetDepth=defaultShallowInsetDepth
self._deepInsetDepth=defaultDeepInsetDepth
self._numSplines=defaultNumSplines
#properties
@Anonymous
def matJointName(self):
return self._matJointName
@matJointName.setter
def matJointName(self, value):
self._matJointName = value
@Anonymous
def shallowInsetLength(self):
return self._shallowInsetLength
@shallowInsetLength.setter
def shallowInsetLength(self, value):
self._shallowInsetLength = value
@Anonymous
def deepInsetLength(self):
return self._deepInsetLength
@deepInsetLength.setter
def deepInsetLength(self, value):
self._deepInsetLength = value
@Anonymous
def boardThickness(self):
return self._boardThickness
@boardThickness.setter
def boardThickness(self, value):
self._boardThickness = value
@Anonymous
def millDiameter(self):
return self._millDiameter
@millDiameter.setter
def millDiameter(self, value):
self._millDiameter = value
@Anonymous
def filletRadius(self):
return self._filletRadius
@filletRadius.setter
def filletRadius(self, value):
self._filletRadius = value
@Anonymous
def shallowInsetDepth(self):
return self._shallowInsetDepth
@shallowInsetDepth.setter
def shallowInsetDepth(self, value):
self._shallowInsetDepth = value
@Anonymous
def deepInsetDepth(self):
return self._deepInsetDepth
@deepInsetDepth.setter
def deepInsetDepth(self, value):
self._deepInsetDepth = value
@Anonymous
def numSplines(self):
return self._numSplines
@numSplines.setter
def numSplines(self, value):
self._numSplines = value
def buildMatJoint(self):
def create_perpendicular_sketch_line(sketch, sketchLine, face):
# Calculate the midpoint of the sketchLine
midX = (sketchLine.startSketchPoint.geometry.x + sketchLine.endSketchPoint.geometry.x) / 2
midY = (sketchLine.startSketchPoint.geometry.y + sketchLine.endSketchPoint.geometry.y) / 2
midPoint = adsk.core.Point3D.create(midX, midY, 0)
# Calculate the direction vector of the sketchLine
lineVec = adsk.core.Vector3D.create(sketchLine.endSketchPoint.geometry.x - sketchLine.startSketchPoint.geometry.x,
sketchLine.endSketchPoint.geometry.y - sketchLine.startSketchPoint.geometry.y, 0)
lineVec.normalize()
# Get the normal of the face
faceEval = face.evaluator
(returnValue, normal) = faceEval.getNormalAtPoint(midPoint)
# Calculate the perpendicular direction within the sketch plane
perpDir = lineVec.crossProduct(normal)
perpDir = perpDir.crossProduct(lineVec)
perpDir.normalize()
# Scale the perpendicular direction to half the length of the sketchLine
halfLength = sketchLine.length / 2
scaledPerpDir = adsk.core.Vector3D.create(perpDir.x, perpDir.y, perpDir.z)
scaledPerpDir.scaleBy(halfLength)
# Create the end point for the perpendicular line
perpLineEnd = midPoint.copy()
perpLineEnd.translateBy(scaledPerpDir)
# Create the perpendicular line in the sketch
return sketch.sketchCurves.sketchLines.addByTwoPoints(midPoint, perpLineEnd)
def create_coordinate_system(newXAxisLine, newYAxisLine):
# Extract start and end points of the new axes lines
newXAxisStart = newXAxisLine.startSketchPoint.geometry
newXAxisEnd = newXAxisLine.endSketchPoint.geometry
newYAxisStart = newYAxisLine.startSketchPoint.geometry
newYAxisEnd = newYAxisLine.endSketchPoint.geometry
# Determine the shared origin
points = [newXAxisStart, newXAxisEnd, newYAxisStart, newYAxisEnd]
sharedOrigin = None
for i in range(len(points)):
for j in range(i + 1, len(points)):
if points[i].isEqualTo(points[j]):
sharedOrigin = points[i]
break
if sharedOrigin:
break
if not sharedOrigin:
raise ValueError("No shared origin found between the provided axes lines.")
# Determine the direction vectors for the new axes
if sharedOrigin.isEqualTo(newXAxisStart):
newXAxisVector = newXAxisStart.vectorTo(newXAxisEnd) # Adjusted this line
else:
newXAxisVector = newXAxisEnd.vectorTo(newXAxisStart)
if sharedOrigin.isEqualTo(newYAxisStart):
newYAxisVector = newYAxisStart.vectorTo(newYAxisEnd) # Adjusted this line for consistency
else:
newYAxisVector = newYAxisEnd.vectorTo(newYAxisStart)
return (sharedOrigin,newXAxisVector,newYAxisVector)
def transform_point_to_new_coordinate_system(point, coords):
sharedOrigin,newXAxisVector, newYAxisVector = coords
# Normalize the vectors to create an orthonormal basis
newXAxisVector.normalize()
newYAxisVector.normalize()
# Create transformation matrix
matrix = adsk.core.Matrix3D.create()
matrix.setWithCoordinateSystem(
sharedOrigin, # Shared origin of the new X and Y axes
newXAxisVector, # X axis
newYAxisVector, # Y axis
newXAxisVector.crossProduct(newYAxisVector) # Z axis (cross product of X and Y)
)
# Remove the matrix inversion if transforming from original to new coordinate system
# matrix.invert()
# Apply the transformation to the point
transformedPoint = point.copy()
transformedPoint.transformBy(matrix)
app.log(f'FROM:{str_vertex(point)} TO: {str_vertex(transformedPoint)}')
return transformedPoint
######BUILD
try:
app.log("buildMatJoint")
global newComp
newComp = createNewComponent()
if newComp is None:
ui.messageBox('New component failed to create', 'New Component Failed')
return
#Calculate Relevant Anatomy
relevantAnatomy = calculateRelevantAnatomyFromSelection(self._selectedEdge,self._selectedBodies)
if relevantAnatomy is None:
ui.messageBox('relevantAnatomy=None')
return None
# Access fusion 360 stuff
product = app.activeProduct
design = adsk.fusion.Design.cast(product)
rootComp = design.rootComponent
constPoints = rootComp.constructionPoints
# Find matching vertices in the relevant edges for projecting.
overlapLineSegStart = find_matching_vertex_in_edges([relevantAnatomy.edgeA,relevantAnatomy.edgeB],relevantAnatomy.edgeOverlap[0])
overlapLineSegEnd = find_matching_vertex_in_edges([relevantAnatomy.edgeA,relevantAnatomy.edgeB],relevantAnatomy.edgeOverlap[1])
# Get the parent component of body A from the relevant anatomy.
parentComp = relevantAnatomy.bodyA.parentComponent
#
#
# Add Face A Inset Sketch
#
#
def createCutoutOnFace(overlapLineSegStart, overlapLineSegEnd,faceToSketch,bodyToCut):
# Create a new sketch on Face A of the relevant anatomy.
sketchA = parentComp.sketches.add(faceToSketch)
#calc midpoint of overlap line
splineIndexLineStart = sketchA.project(overlapLineSegStart).item(0)
splineIndexLineEnd = sketchA.project(overlapLineSegEnd).item(0)
splineIndexLine = sketchA.sketchCurves.sketchLines.addByTwoPoints(splineIndexLineStart, splineIndexLineEnd)
splineIndexLineMidPoint = add_point_at_midpoint_of_line(sketchA,splineIndexLine)
#Create a coodinate system around the midpoint of the overlap line.
splineIndexXAxis = create_perpendicular_sketch_line(sketchA, splineIndexLine, faceToSketch)
splineIndexYAxis = sketchA.sketchCurves.sketchLines.addByTwoPoints(splineIndexLineMidPoint, splineIndexLineEnd)
coords = create_coordinate_system(splineIndexXAxis,splineIndexYAxis)
#Create Expressions
xDimLeftRect = f'({self.shallowInsetLength})'
xDimRightRect= f'({self.deepInsetLength})'
xRightSideOfShape = f'({self.deepInsetLength}) + ({self.shallowInsetLength})'
xLeftCirclesCenter = f'({self.shallowInsetLength}) + (({self.millDiameter}) / 2)'
xRightCirclesCenter = f'({xRightSideOfShape}) - (({self.millDiameter}) / 2)'
millCircleRadius =f'(({self.millDiameter}) / 2)'
yTopOfCutout = f'({self.boardThickness/2})'
yBottomOfCutout = f'(-{self.boardThickness/2})'
yDimRect = f'({self.boardThickness})'
unitsManager = app.activeProduct.unitsManager
#Evaluate expressions for initial positions of geometry
yDimTopRectVal = unitsManager.evaluateExpression(yTopOfCutout)
yDimBottomRectVal = unitsManager.evaluateExpression(yBottomOfCutout)
xDimLeftRectVal = unitsManager.evaluateExpression(xDimLeftRect)
xDimRightRectVal = unitsManager.evaluateExpression(xDimRightRect)
circleDiameterVal = self.millDiameter
xRightSideOfShapeVal = unitsManager.evaluateExpression(xRightSideOfShape)
xLeftCirclesCenterVal = unitsManager.evaluateExpression(xLeftCirclesCenter)
xRightCirclesCenterVal = unitsManager.evaluateExpression(xRightCirclesCenter)
radiusVal = unitsManager.evaluateExpression(millCircleRadius)
#3D Points
#rect points
tll_pt3d = adsk.core.Point3D.create(-xDimLeftRectVal, yDimTopRectVal, 0)
tl_pt3d = adsk.core.Point3D.create(0, yDimTopRectVal, 0)
tm_pt3d = adsk.core.Point3D.create(xDimLeftRectVal, yDimTopRectVal, 0)
tr_pt3d = adsk.core.Point3D.create(xRightSideOfShapeVal, yDimTopRectVal, 0)
bll_pt3d = adsk.core.Point3D.create(-xDimLeftRectVal, yDimBottomRectVal, 0)
bl_pt3d = adsk.core.Point3D.create(0, yDimBottomRectVal, 0)
bm_pt3d = adsk.core.Point3D.create(xDimLeftRectVal, yDimBottomRectVal, 0)
br_pt3d = adsk.core.Point3D.create(xRightSideOfShapeVal, yDimBottomRectVal, 0)
#circle points
tl_circle_pt3d = adsk.core.Point3D.create(xLeftCirclesCenterVal,yDimTopRectVal,0)
tr_circle_pt3d = adsk.core.Point3D.create(xRightCirclesCenterVal,yDimTopRectVal,0)
bl_circle_pt3d = adsk.core.Point3D.create(xLeftCirclesCenterVal,yDimBottomRectVal,0)
br_circle_pt3d = adsk.core.Point3D.create(xRightCirclesCenterVal,yDimBottomRectVal,0)
#Rotate Points to coordinate system of face
tll_pt3d = transform_point_to_new_coordinate_system(tll_pt3d,coords)
tl_pt3d = transform_point_to_new_coordinate_system(tl_pt3d,coords)
tm_pt3d = transform_point_to_new_coordinate_system(tm_pt3d,coords)
tr_pt3d = transform_point_to_new_coordinate_system(tr_pt3d,coords)
bll_pt3d = transform_point_to_new_coordinate_system(bll_pt3d,coords)
bl_pt3d = transform_point_to_new_coordinate_system(bl_pt3d,coords)
bm_pt3d = transform_point_to_new_coordinate_system(bm_pt3d,coords)
br_pt3d = transform_point_to_new_coordinate_system(br_pt3d,coords)
tl_circle_pt3d = transform_point_to_new_coordinate_system(tl_circle_pt3d,coords)
tr_circle_pt3d = transform_point_to_new_coordinate_system(tr_circle_pt3d,coords)
bl_circle_pt3d = transform_point_to_new_coordinate_system(bl_circle_pt3d,coords)
br_circle_pt3d = transform_point_to_new_coordinate_system(br_circle_pt3d,coords)
##########ADD POINTS TO SKETCH
#circle centers
tl_circle_pt = sketchA.sketchPoints.add(tl_circle_pt3d)
tr_circle_pt = sketchA.sketchPoints.add(tr_circle_pt3d)
bl_circle_pt = sketchA.sketchPoints.add(bl_circle_pt3d)
br_circle_pt = sketchA.sketchPoints.add(br_circle_pt3d)
#rectangle vertices
tll_pt = sketchA.sketchPoints.add(tll_pt3d)
tl_pt = sketchA.sketchPoints.add(tl_pt3d)
tm_pt = sketchA.sketchPoints.add(tm_pt3d)
tr_pt = sketchA.sketchPoints.add(tr_pt3d)
bll_pt = sketchA.sketchPoints.add(bll_pt3d)
bl_pt = sketchA.sketchPoints.add(bl_pt3d)
bm_pt = sketchA.sketchPoints.add(bm_pt3d)
br_pt = sketchA.sketchPoints.add(br_pt3d)
text_point = adsk.core.Point3D.create(0, 0, 0) # Midpoint between point1 and point2 for the text
##add shapes to sketch
circles = sketchA.sketchCurves.sketchCircles
tl_circle = circles.addByCenterRadius(tl_circle_pt3d, radiusVal)
tr_circle = circles.addByCenterRadius(tr_circle_pt3d, radiusVal)
bl_circle = circles.addByCenterRadius(bl_circle_pt3d, radiusVal)
br_circle = circles.addByCenterRadius(br_circle_pt3d, radiusVal)
tl_tm_line = sketchA.sketchCurves.sketchLines.addByTwoPoints(tl_pt, tm_pt)
tr_tm_line = sketchA.sketchCurves.sketchLines.addByTwoPoints(tr_pt, tm_pt)
bl_bm_line = sketchA.sketchCurves.sketchLines.addByTwoPoints(bl_pt, bm_pt)
br_bm_line = sketchA.sketchCurves.sketchLines.addByTwoPoints(br_pt, bm_pt)
tl_bl_line = sketchA.sketchCurves.sketchLines.addByTwoPoints(tl_pt, bl_pt)
tm_bm_line = sketchA.sketchCurves.sketchLines.addByTwoPoints(tm_pt, bm_pt)
tr_br_line = sketchA.sketchCurves.sketchLines.addByTwoPoints(tr_pt, br_pt)
tll_tl_line = sketchA.sketchCurves.sketchLines.addByTwoPoints(tll_pt, tl_pt)
bll_bl_line = sketchA.sketchCurves.sketchLines.addByTwoPoints(bll_pt, bl_pt)
tll_bll_line = sketchA.sketchCurves.sketchLines.addByTwoPoints(bll_pt, tll_pt)
#Constraints
#
#
#
tl_tm_line_perpendicular = sketchA.geometricConstraints.addPerpendicular(splineIndexLine, tl_tm_line)
bl_bm_line_perpendicular = sketchA.geometricConstraints.addPerpendicular(splineIndexLine, bl_bm_line)
top_colinear = sketchA.geometricConstraints.addCollinear(tl_tm_line, tr_tm_line)
bottom_colinear = sketchA.geometricConstraints.addCollinear(bl_bm_line, br_bm_line)
tll_colinear = sketchA.geometricConstraints.addCollinear(tll_tl_line, tl_tm_line)
bll_colinear = sketchA.geometricConstraints.addCollinear(bll_bl_line, bl_bm_line)
tl_circle_tangent = sketchA.geometricConstraints.addTangent(tm_bm_line, tl_circle)
bl_circle_tangent = sketchA.geometricConstraints.addTangent(tm_bm_line, bl_circle)
tr_circle_tangent = sketchA.geometricConstraints.addTangent(tr_br_line, tr_circle)
br_circle_tangent = sketchA.geometricConstraints.addTangent(tr_br_line, br_circle)
#Dimensions
#
#
#
#circles
tl_circle_dim = sketchA.sketchDimensions.addDistanceDimension(
tl_circle_pt,
tm_pt,
adsk.fusion.DimensionOrientations.AlignedDimensionOrientation,
tl_pt.geometry,
True)
tl_circle_dim.parameter.expression = millCircleRadius
bl_circle_dim = sketchA.sketchDimensions.addDistanceDimension(
bl_circle_pt,
bm_pt,
adsk.fusion.DimensionOrientations.AlignedDimensionOrientation,
bl_pt.geometry,
True)
bl_circle_dim.parameter.expression = millCircleRadius
tr_circle_dim = sketchA.sketchDimensions.addDistanceDimension(
tr_circle_pt,
tr_pt,
adsk.fusion.DimensionOrientations.AlignedDimensionOrientation,
tr_pt.geometry,
True)
tr_circle_dim.parameter.expression = millCircleRadius
br_circle_dim = sketchA.sketchDimensions.addDistanceDimension(
br_circle_pt,
br_pt,
adsk.fusion.DimensionOrientations.AlignedDimensionOrientation,
br_pt.geometry,
True)
br_circle_dim.parameter.expression = millCircleRadius
#rects
ySketchDim = sketchA.sketchDimensions.addDistanceDimension(
tl_pt,
bl_pt,
adsk.fusion.DimensionOrientations.AlignedDimensionOrientation,
bl_pt.geometry,
True)
ySketchDim.parameter.expression = yDimRect
x_tll_Dim = sketchA.sketchDimensions.addDistanceDimension(
tll_pt,
tl_pt,
adsk.fusion.DimensionOrientations.AlignedDimensionOrientation,
tl_pt.geometry,
True)
x_tll_Dim.parameter.expression = xDimLeftRect
x_bll_Dim = sketchA.sketchDimensions.addDistanceDimension(
bll_pt,
bl_pt,
adsk.fusion.DimensionOrientations.AlignedDimensionOrientation,
bl_pt.geometry,
True)
x_bll_Dim.parameter.expression = xDimLeftRect
x_tl_Dim = sketchA.sketchDimensions.addDistanceDimension(
tl_pt,
tm_pt,
adsk.fusion.DimensionOrientations.AlignedDimensionOrientation,
tl_pt.geometry,
True)
x_tl_Dim.parameter.expression = xDimLeftRect
x_bl_Dim = sketchA.sketchDimensions.addDistanceDimension(
bl_pt,
bm_pt,
adsk.fusion.DimensionOrientations.AlignedDimensionOrientation,
bl_pt.geometry,
True)
x_bl_Dim.parameter.expression = xDimLeftRect
x_tr_Dim = sketchA.sketchDimensions.addDistanceDimension(
tr_pt,
tm_pt,
adsk.fusion.DimensionOrientations.AlignedDimensionOrientation,
tr_pt.geometry,
True)
x_tr_Dim.parameter.expression = xDimRightRect
x_br_Dim = sketchA.sketchDimensions.addDistanceDimension(
br_pt,
bm_pt,
adsk.fusion.DimensionOrientations.AlignedDimensionOrientation,
br_pt.geometry,
True)
x_br_Dim.parameter.expression = xDimRightRect
def find_all_combination_profiles(sketch, sketchElements, tolerance=1e-6):
"""
Finds profiles in a sketch that match the bounding boxes of all possible combinations
of given sketch elements.
Parameters:
sketch (adsk.fusion.Sketch): The sketch containing the profiles.
sketchElements (list): A list of sketch elements (e.g., lines, circles).
tolerance (float): A tolerance value for comparing bounding boxes.
Returns:
adsk.core.ObjectCollection: An ObjectCollection of matching profiles.
"""
matching_profiles = adsk.core.ObjectCollection.create()
# Generate all possible combinations of elements
for r in range(1, len(sketchElements) + 1):
for combination in combinations(sketchElements, r):
# Calculate the bounding box for the current combination
combined_bbox = calculate_combined_bounding_box(combination)
combined_min_x = combined_bbox.minPoint.x
combined_min_y = combined_bbox.minPoint.y
combined_max_x = combined_bbox.maxPoint.x
combined_max_y = combined_bbox.maxPoint.y
# Search for matching profiles in the sketch
for profile in sketch.profiles:
bbox = profile.boundingBox
if (abs(bbox.minPoint.x - combined_min_x) < tolerance and
abs(bbox.minPoint.y - combined_min_y) < tolerance and
abs(bbox.maxPoint.x - combined_max_x) < tolerance and
abs(bbox.maxPoint.y - combined_max_y) < tolerance):
if profile not in matching_profiles:
matching_profiles.add(profile)
return matching_profiles
# Find profiles to extrude
shallowProfileSketchComponents = [tll_pt, tl_pt, bll_pt, bl_pt]
target_profiles = find_all_combination_profiles(sketchA, shallowProfileSketchComponents)
app.log(f"Profiles found: {target_profiles.count}")
app.log(f"sketchA parent {sketchA.parentComponent}")
# Proceed only if there are profiles to extrude
if len(target_profiles.asArray()) > 0:
for profile in target_profiles:
# Log details about each profile
area = profile.areaProperties().area
app.log(f"Profile area: {area}")
extrudes = rootComp.features.extrudeFeatures
# Create an extrude input for the ObjectCollection of profiles
extrudeInput = extrudes.createInput(target_profiles.item(0),adsk.fusion.FeatureOperations.CutFeatureOperation)
extrudeInput.participantBodies = [bodyToCut]
mm100 = adsk.core.ValueInput.createByString("-100 mm")
extent_distance = adsk.fusion.DistanceExtentDefinition.create(mm100)
extrudeInput.setOneSideExtent(extent_distance, adsk.fusion.ExtentDirections.NegativeExtentDirection)
# Add the extrude feature to the model
extrusion = extrudes.add(extrudeInput)
else:
app.log("Matching profiles not found.")
sketchA = createCutoutOnFace(overlapLineSegStart,overlapLineSegEnd,relevantAnatomy.adjFaceA,relevantAnatomy.bodyA)
sketchB = createCutoutOnFace(overlapLineSegStart,overlapLineSegEnd,relevantAnatomy.adjFaceB,relevantAnatomy.bodyB)
except Exception as e:
app.log(f"Error in add_parametric_point_on_line: {str(e)}")
if ui:
ui.messageBox('Failed to compute matJoint:\n{}'.format(traceback.format_exc()))