# How to check if 2d planes inside each other intersect using Python?

I want to modify a script that I am working on such that it ends a loop when there is an intersection/collision (I'm not sure how best to describe the actual intersection event in this particular situation since the objects are inside each other) between two planes (plane A and plane B). The loop rotates plane A and at a certain degree of rotation plane A intersects plane B, which is static.

I've seen/tried to use solutions that involve BHVtree but I haven't been able to get them working. I've also tried boolean operations (intersect, subtract, etc), but they don't seem to do anything differently from when the objects intersect (or 'stop intersecting'?).

I am working with primative 2d planes and plane A is inside plane B. In the below wireframe images, the 'yellow' selected object and the 'red' selected object are what I am referring to as plane A and plane B, respectively.

This image shows a given rotation in which plane A DOES NOT intersect with plane B

This image shows a second rotation in which plane A DOES intersect with plane B. I would like to be able to identity when this event occurs within my script.

UPDATED: Based on batFINGER's suggestion, I looked into intersect_line_sphere_2d from mathutils.geometry. Thank you very much for pointing me to it.

I wrote a script based on that particular function, which works but not correctly. I think my math/trig is off somewhere because the angle reported is larger than expected, and the angle should decrease as the scale is increased.

Below is the script:

# testing script for predicting rotation-based collision angle thresholds
# output is not correct.  Increasing scale should give lower roll angle tolerance, also roll angle values are not accurate as compared by other methods.  Code needs to be adjusted.

#--------
# imports
#--------
import bpy
import numpy as np
from math import cos
from math import sin
import mathutils
from mathutils.geometry import intersect_line_sphere_2d

#----------
# functions
#----------
# function converts from degrees to radians
result = (num) * (np.pi/180)
return result

# function converts from radians to degrees
def degrees(num):
result = (num) * (180/np.pi)
return result

#----------
# variables
#----------
relativeRadius = 0.3 #relevant for actual objects being tested
scalePercentage = 0.80 #percentage

theta = 225.0 #represents starting point on the circumference of the rotational pathway, e.g., bottom left corner of square

# variables for the intersect_line_sphere_2d function
lineA = mathutils.Vector (( -radiusOne+leftAdjustment, -radiusOne))  #bottom left and moved to the right to compensate for the left adjustment that is typically applied to the actual object
lineB = mathutils.Vector (( -radiusOne+leftAdjustment, radiusOne))  #top left and moved to the right to compensate for the left adjustment that is typically applied to the actual object
locationCircle = mathutils.Vector ((xPos, yPos))
radiusCircle = 0.0000001 #very small to make it a 2d 'point' rather than a circle

#amount of change in degrees for testing roll angle tolerance
incrementsInDegrees= 0
numberOfTests=0 #added in case scale is TOO SMALL in which case the loop would continue without end and probably crash blender; an alternative  would be to just use something like a 90 degree angle check as security against an infinite loop

#----------
# main loop:
#----------
#set beginning of intersect_line_ function to starting position of object bottom left corner
i = intersect_line_sphere_2d(lineA, lineB, locationCircle, radiusCircle)
while i[0] == None and i[0] == None and numberOfTests < 50000:
numberOfTests += 1
incrementsInDegrees += 0.01
locationCircle = mathutils.Vector ((xPos, yPos))
i = intersect_line_sphere_2d(lineA, lineB, locationCircle, radiusCircle)

#--------------------------
# print results to console:
#--------------------------
rollAngleToleranceInDegrees = str(incrementsInDegrees)
numberOfAnglesAttempted = str(numberOfTests)
print("roll angle tolerance is: %s" % (rollAngleToleranceInDegrees))
print("number of angles attempted: %s" % (numberOfAnglesAttempted))

• Assume all planes in the same plane? Suggest: Check out the routines in mathutls.geometry. in particular intersect line with circle. Given a pivot point calculate the paths of each corner (the circle) , where they hit "the lines" or edges of other planes., and hence the range of rotation possible before you rotate, rather than checking for collision while you are rotating Oh and I always recommend pasting script , or link to, of what you have so far – batFINGER Jun 5 at 11:48
• Thanks batFINGER, I'll check out the routine you've suggested and update shortly. And, in the future, I'll try to include more information about my current progress. – user70711 Jun 5 at 12:31
• @batFINGER, your suggestion was great. I've implemented a code (see edited question above) based on that function. It works, but it doesn't correctly work. The data it provides is not valid. I'm hoping that its just some minor math issues or something I just missed and can be fixed easily. If you have time and can spot anything amiss in the code, I'd appreciate hearing from you. I'll continue working on it and update if I have any changes as well. Thanks again for your help so far. – user70711 Jun 5 at 16:40

Calculate the range of rotation

Given a pivot point calculate the paths of each corner (the circle) , where they hit "the lines" or edges of other planes., and hence the range of rotation possible before you rotate, rather than checking for collision while you are rotating

Assumptions:

• We have a defined starting state where the inner rectangle is inside the outer.
• Both planes have constant z (xy planes z normal)

A sample script to illustrate.

• Made a bmesh from input planes, and transformed to global coordinates.
• For each vertex (corner) of inner square, scribe a circle from cursor. (curve added to illustrate)
• For each edge of the outer circle see if it intersects the circle, using mathutils.geometry.intersect_line_sphere_2d(...)
• If there's a hit an empty is added to illustrate, and the signed 2d angle from corner radial to hit is calculated.
• The range of rotation will be maximum negative and minimum positive angle result.
• A simple animation to illustrate is keyframed, original z rotation at frame 1, hit the two ranges, and return to median of both.
• If there are not two planes "Plane" and "Plane.001" or there are no hits the script will throw assert errors, in lieu of handling them better.

To run

• Add two planes to scene, with default z up, named "Plane" and "Plane.001". Make "Plane" the inner rectangle.
• Set the cursor position to pivot point of inner square.
• Run the script

Script (written for 2.8)

import bpy
import bmesh
from math import degrees
from mathutils import Vector
from mathutils.geometry import intersect_line_sphere_2d as ils
bpy.ops.object.select_by_type(type='CURVE')
bpy.ops.object.select_by_type(type='EMPTY', extend=True)

bpy.ops.object.delete()

context = bpy.context
scene = context.scene

inner = scene.objects.get("Plane")
outer = scene.objects.get("Plane.001")
print("-" * 72)
assert(inner and outer)

bm = bmesh.new()
bm.from_mesh(inner.data)
bmesh.ops.transform(bm,
matrix=inner.matrix_world,
verts=bm.verts
)
inner_verts = bm.verts[:]
bm.from_mesh(outer.data)
bmesh.ops.transform(bm,
matrix=outer.matrix_world,
verts=bm.verts[-4:]
)
outer_edges = bm.edges[-4:]
sphere_co = scene.cursor.location
angles = []
for v in inner_verts:
print(v.index, v.co)
r = (v.co - sphere_co).xy
location=sphere_co,
)
for e in outer_edges:
line_a, line_b = [v.co.xy for v in e.verts]

hits = [Vector(h) for h in ils(line_a, line_b, sphere_co.xy, sphere_radius) if h is not None]
for hit in hits:
location=hit.to_3d())
angles.append(r.angle_signed(Vector(hit) - sphere_co.xy))

assert(len(angles))
cw = min(a for a in angles if a >=0)
ccw = max(a for a in angles if a < 0)
bpy.ops.object.select_all(action='DESELECT')

context.view_layer.objects.active = inner
inner.select_set(True, view_layer=context.view_layer)
bpy.ops.object.origin_set(type='ORIGIN_CURSOR')
outer.select_set(True)
rotz = inner.rotation_euler.z

keyframes = ((0, rotz),
(10, rotz - cw),
(40, rotz - cw),
(50, rotz),
(60, rotz - ccw),
(90, rotz - ccw),
(100, rotz - (cw + ccw) / 2),

)

for frame, value in keyframes:
for off in (3,):
f = 1 + frame / off
inner.rotation_euler.z = value
inner.keyframe_insert("rotation_euler", index=2, frame=f)
scene.frame_set(1)
print(f"{degrees(ccw)} to {degrees(cw)}" )

• thank you for the detailed solution. Yesterday I also finished up writing a script which resolves my issue. I took your advice (" Given a pivot point calculate the paths of each corner (the circle) , where they hit "the lines" or edges of other planes., and hence the range of rotation possible before you rotate, rather than checking for collision while you are rotating") and solved it without using anything in Blender. I just calculated the important locations and then rotated by changing x and y positions using trig functions. – user70711 Jun 7 at 6:44
• By not actually generating anything within blender (basically just running a python script outside of Blender), I was able to test something like 3 million angles with a runtime of ~1.2s. Your suggestion helped me realize the way to solve it this way. Thank you again for your help and I am sure that the answer that you've provided will help others who have similar problems. – user70711 Jun 7 at 6:46