Skip to content

Instantly share code, notes, and snippets.

@donovankeith
Created October 21, 2016 20:22
Show Gist options
  • Save donovankeith/6ff1d342bc09ca1aacb1c0c549cdd7a7 to your computer and use it in GitHub Desktop.
Save donovankeith/6ff1d342bc09ca1aacb1c0c549cdd7a7 to your computer and use it in GitHub Desktop.
Code for a Cinema 4D Python Expression Tag that automatically selects edges based on the angle between adjoining faces.
"""Select Phong Edges Expression
Selects any edges w/ broken phong shading.
Last Modified: 2016-10-21
v0.0.1
By Donovan Keith for MAXON USA
Usage Instructions
-------------------
1. Add a Python Expression tag to a polygon object.
2. Paste in this code.
Known Limitations
-----------------
- Doesn't support custom point normals.
- Calculates based on the first triangle in a given polygon, results may be unpredictable with non-coplanar NGons.
License
--------
Feel free to re-use this code in any personal or commercial projects. Credit would be nice, but isn't mandatory.
"""
import c4d
import math
def YieldEdges(op, nbr):
"""Generates a list of a,b points in all edges.
"""
vadr = op.GetAllPolygons()
for i in xrange(op.GetPolygonCount()):
pli = nbr.GetPolyInfo(i)
for side in xrange(4): # # test all 4 sides of a polygon
# Only proceed if edge has not already been processed
# and edge really exists (for triangles side 2 from c..d does not exist as c==d)
if pli["mark"][side] or side==2 and vadr[i].c==vadr[i].d: continue
# One can also skip the side==2 && vadr[i].c==vadr[i].d test as pli["mark"][2] is always True for triangles
if side==0:
a=vadr[i].a; b=vadr[i].b
elif side==1:
a=vadr[i].b; b=vadr[i].c
elif side==2:
a=vadr[i].c; b=vadr[i].d
elif side==3:
a=vadr[i].d; b=vadr[i].a
yield a, b
def GetEdgeAngle(poly1, poly2, points):
"""Returns the angle between two polygons.
Limitation: only takes the first triangle into account.
poly1, poly2: CPolygons
Return: angle in Radians
Source Reference:
http://www.plugincafe.com/forum/forum_posts.asp?TID=12869&KW=polygon+normal&PID=50916#50916
"""
a1,b1,c1 = points[poly1.a],points[poly1.b],points[poly1.c]
n1 = (b1 - a1).Cross(c1 - a1).GetNormalized()
a2,b2,c2 = points[poly2.a],points[poly2.b],points[poly2.c]
n2 = (b2 - a2).Cross(c2 - a2).GetNormalized()
angle_rad = c4d.utils.GetAngle(n1,n2)
return angle_rad
def GetPhongTag(obj):
"""Returns the first PhongTag on object."""
if not obj:
return
tags = obj.GetTags()
for tag in tags:
if tag.GetType() == c4d.Tphong:
return tag
def GetPhongAngle(obj):
"""Returns phong limit angle of obj"""
if not obj:
return
phong_tag = GetPhongTag(obj)
if not phong_tag:
return 0.0
angle_limit = phong_tag[c4d.PHONGTAG_PHONG_ANGLELIMIT]
if angle_limit:
return phong_tag[c4d.PHONGTAG_PHONG_ANGLE]
else:
return c4d.utils.Rad(180.0)
def PhongSelectEdges(obj, phong_deg=None):
"""Selects the edges whose adjoining polys have a > phong_deg angle
between them. Also selects broken phong edges. If phong_deg is not
specified, the function will calculate based on the Phong tag on the
object.
Note: no Undo support or Update calls are made.
"""
points = obj.GetAllPoints()
polys = obj.GetAllPolygons()
# Calculate the phong threshold using the specified angle, or if None the Phong tag on the object
phong_angle = None
if phong_deg is not None:
phong_angle = c4d.utils.Rad(phong_deg)
else:
phong_angle = GetPhongAngle(obj)
if phong_angle is None:
phong_angle = 0
# Select Manually Broken Edges
neighbor = c4d.utils.Neighbor()
neighbor.Init(obj)
edge_count = neighbor.GetEdgeCount()
phong_break_edges = obj.GetSelectedEdges(neighbor, c4d.EDGESELECTIONTYPE_PHONG)
# Go through every edge
for edge_index, edge in enumerate(YieldEdges(obj, neighbor)):
point_a, point_b = edge
# Get the neighbor polygons
poly1_index, poly2_index = neighbor.GetEdgePolys(point_a, point_b)
poly1 = polys[poly1_index]
poly2 = polys[poly2_index]
# Calculate the difference between them
angle = GetEdgeAngle(poly1, poly2, points)
# If difference is over a threshold, select the edge
if angle >= phong_angle:
phong_break_edges.Select(edge_index)
# Select the Broken Phong Edges + Auto-Broken Edges
obj.SetSelectedEdges(neighbor, phong_break_edges, c4d.EDGESELECTIONTYPE_SELECTION)
def main():
if not op:
return
# Retrieve the polygon object
obj = op.GetObject()
if not obj or not obj.IsInstanceOf(c4d.Opolygon):
return
PhongSelectEdges(obj)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment