Cinema 4D - my Python scripts - expand splines
Jump to navigation
Jump to search
About
NOTE: This page is a daughter page of: Cinema 4D
This page contains code for a Python script I've written for Cinema 4D. To understand how it compiles see Cinema 4D - my Python scripts.
expand_splines.py
expand_splines.py
# For each selected open spline (line), turns it into a thick
# closed spline (polygon) along the path of the original line.
# No additional chamfer points are added, so very sharp corners will
# look funny - resulting spline will have twice as many points.
# Alternatively can also fold the spline back on itself in place
# without duplicating the first or last line.
import c4d
from c4d import gui
import math
# Unique id numbers for each of the GUI elements
LBL_USAGE = 1000
LBL_INFO1 = 1001
LBL_INFO2 = 1002
GROUP_OPTS = 10000
NUMEDIT_THICKNESS = 10001
CHK_CLOSE_SPLINE = 10003
CHK_MAKE_COPY = 10004
CMB_ACTION = 20000
CMB_ACTION_EXPAND = 20001
CMB_ACTION_REVERSE = 20002
class OptionsDialog(gui.GeDialog):
""" Dialog for expanding a open spline.
"""
def CreateLayout(self):
self.SetTitle('Expand Open Spline')
self.AddMultiLineEditText(LBL_USAGE, c4d.BFH_SCALEFIT, inith=40, initw=500,
style=c4d.DR_MULTILINE_READONLY)
self.SetString(LBL_USAGE,
"USAGE: For any selected open splines, makes them\n"
" thick closed polygons of specified thickness")
# Dropdown and thickness option:
self.GroupBegin(GROUP_OPTS, c4d.BFH_SCALEFIT, 2, 2)
self.AddStaticText(LBL_INFO1, c4d.BFH_LEFT, name='Action: ')
self.AddComboBox(CMB_ACTION, c4d.BFH_SCALEFIT)
self.AddChild(CMB_ACTION, CMB_ACTION_EXPAND, "expand spline in XZ")
self.AddChild(CMB_ACTION, CMB_ACTION_REVERSE, "reverse on itself")
self.SetInt32(CMB_ACTION, CMB_ACTION_EXPAND) # Set default action.
self.AddStaticText(LBL_INFO2, c4d.BFH_LEFT, name='Thickness: ')
self.AddEditNumber(NUMEDIT_THICKNESS, c4d.BFH_SCALEFIT)
self.SetReal(NUMEDIT_THICKNESS, 10)
self.GroupEnd()
self.AddSeparatorH(c4d.BFH_SCALE);
# Checkbox options:
self.AddCheckbox(CHK_CLOSE_SPLINE, c4d.BFH_SCALEFIT,
initw=1, inith=1, name="close spline when done")
self.SetBool(CHK_CLOSE_SPLINE, True)
self.AddCheckbox(CHK_MAKE_COPY, c4d.BFH_SCALEFIT,
initw=1, inith=1, name="make new copy")
self.SetBool(CHK_MAKE_COPY, True)
self.AddSeparatorH(c4d.BFH_SCALE);
# Buttons - an Ok and Cancel button:
self.AddDlgGroup(c4d.DLG_OK|c4d.DLG_CANCEL);
self.ok = False
return True
# React to user's input:
def Command(self, id, msg):
if id==c4d.DLG_CANCEL:
self.Close()
elif id==c4d.DLG_OK:
self.ok = True
action = self.GetInt32(CMB_ACTION)
self.option_action_expand = action == CMB_ACTION_EXPAND
self.option_action_reverse = action == CMB_ACTION_REVERSE
self.option_thickness = self.GetReal(NUMEDIT_THICKNESS)
self.option_close_spline = self.GetBool(CHK_CLOSE_SPLINE)
self.option_make_copy = self.GetBool(CHK_MAKE_COPY)
self.Close()
return True
def get_yaw(start_pt, end_pt):
"""Determines the azimuth from the given start to end point.
Rotation is around XZ where -X = 0
Args:
start_pt: c4d.Vector representing start point.
end_pt: c4d.Vector representing end point.
Returns:
yaw angle in degrees.
"""
rad = math.atan2(end_pt.z - start_pt.z, end_pt.x - start_pt.x)
return math.degrees(rad)
def angle_formed_by_three_points_xy(pt1, pt2, pt3):
"""Determines the theta (in degrees) between 3 connected points.
pt3 o
/
/ theta
pt2 o---------o pt1
Args:
pt1: c4d.Vector representing point before middle point.
pt2: c4d.Vector representing middle point.
pt3: c4d.Vector representing point after middle point.
Returns:
angle between 0 and 360.
"""
line1_yaw = get_yaw(pt1, pt2)
line2_yaw = get_yaw(pt3, pt2)
return (line2_yaw - line1_yaw) % 360.0
def get_point_rel_to_end_xy(start_pt, end_pt, dist_offset, rel_angle):
"""Get a point 'dist_offset' from 'end_pt' at an angle relative to the line.
Example: if dist_offset=2 and rel_angle=90:
o <-- the point returned would be here
start_pt o-----------o end_pt
Args:
start_pt: c4d.Vector representing start point.
end_pt: c4d.Vector representing end point.
dist_offset: Numeric distance to offset away from end_pt.
rel_angle: Angle relative from the direction of the line to use in
displacing the returned point. 0 is straight (in direction of line).
Returns:
angle between 0 and 360.
"""
yaw = get_yaw(start_pt, end_pt)
theta = math.radians(yaw + rel_angle)
offset_x = dist_offset * math.cos(theta)
offset_z = dist_offset * math.sin(theta)
return c4d.Vector(offset_x + end_pt.x, end_pt.y, offset_z + end_pt.z)
def get_chamfer_point_xy(pt1, pt2, pt3, offset):
"""Get a chamfer on the outside of a point connected to two other points.
- - - o <-- chamfer point
thickness / (for this case would be 45 degress offset)
/
pt1 o-------o pt2
|
|
o pt3
Args:
pt1: c4d.Vector representing point before middle point.
pt2: c4d.Vector representing middle point.
pt3: c4d.Vector representing point after middle point.
offset: Numerical value representing thickness to base
offset on. A sharper turn will mean further offset.
Returns:
c4d.Vector representing chamfer point.
"""
if pt1 == pt2:
return get_point_rel_to_end_xy(
pt3, pt2, offset, -90)
if pt2 == pt3:
return get_point_rel_to_end_xy(
pt1, pt2, offset, 90)
angle_at_curr = 360 - angle_formed_by_three_points_xy(pt1, pt2, pt3)
angle_inset = angle_at_curr / 2.0
rad = math.sin(math.radians(angle_inset)) # Radians
dist_from_corner = offset
if rad != 0:
dist_from_corner = offset / rad
return get_point_rel_to_end_xy(
pt1, pt2, dist_from_corner, 180.0-angle_inset)
def expand_spline_xz(spline, thickness):
"""Takes a 'spline' object, and expands it by 'thickness' in XZ.
Args:
spline: Open spline object to expand.
thickness: Number representing thickness/diameter of final line.
Returns:
number of points added.
"""
if (spline == None or not spline.CheckType(c4d.Ospline) or
spline.GetPointCount() < 2):
return 0;
len_orig = spline.GetPointCount()
len_new = len_orig * 2
new_spline = spline
new_spline.ResizeObject(len_new)
for i in range(0,len_orig):
p_prev = spline.GetPoint(i) # Previous point.
p_curr = spline.GetPoint(i) # Current point.
p_next = spline.GetPoint(i) # Next point.
if i > 0:
p_prev = spline.GetPoint(i-1)
if i < len_orig-1:
p_next = spline.GetPoint(i+1)
p_mid_cw = get_chamfer_point_xy(p_prev, p_curr, p_next, thickness / 2.0)
p_mid_ccw = get_chamfer_point_xy(p_next, p_curr, p_prev, thickness / 2.0)
new_spline.SetPoint(i, p_mid_cw)
new_spline.SetPoint(len_new-i-1, p_mid_ccw)
doc.AddUndo(c4d.UNDOTYPE_CHANGE, spline)
spline = new_spline
return len_new - len_orig;
def fold_spline_on_itself(spline):
"""Takes a 'spline' object, and appends points which backtrack to the first.
If the origional spline has points at positions: A,B,C,D
The new spline append s(len*2-2) points and becomes: A,B,C,D,C,B
Neither the last or first point are not repeated.
Args:
spline: Spline object to fold back on itself by appending.
Returns:
number of points added.
"""
if (spline == None or not spline.CheckType(c4d.Ospline) or
spline.GetPointCount() < 3):
return 0;
len_orig = spline.GetPointCount()
len_new = (len_orig) * 2 - 2
doc.AddUndo(c4d.UNDOTYPE_CHANGE, spline)
spline.ResizeObject(len_new)
for i in range(1, len_orig-1):
e = len_new - i # Working back from last point.
point = spline.GetPoint(i)
new_point = c4d.Vector(point.x, point.y, point.z)
spline.SetPoint(e, new_point)
return len_new - len_orig;
def main():
# Get the selected objects, including children.
selection = doc.GetActiveObjects(c4d.GETACTIVEOBJECTFLAGS_CHILDREN)
if len(selection) <= 0:
gui.MessageDialog('Must select spline object(s)!')
return
# Open the options dialogue to let users choose their options.
dlg = OptionsDialog()
dlg.Open(c4d.DLG_TYPE_MODAL, defaultw=300, defaulth=50)
if not dlg.ok:
return
doc.StartUndo() # Start undo block.
num_splines_changed = 0
for i in range(0,len(selection)):
spline = selection[i]
if not spline.CheckType(c4d.Ospline) or spline.GetPointCount() < 2:
continue
num_splines_changed += 1
# Make copy if needed:
if dlg.option_make_copy:
new_spline = spline.GetClone()
doc.InsertObject(new_spline, pred=spline)
doc.AddUndo(c4d.UNDOTYPE_NEW, new_spline)
spline = new_spline
# Apply chosen action:
points_added = 0
if dlg.option_action_expand:
points_added = expand_spline_xz(spline, dlg.option_thickness)
elif dlg.option_action_reverse:
points_added = fold_spline_on_itself(spline)
# Close spline:
if dlg.option_close_spline:
spline[c4d.SPLINEOBJECT_CLOSED] = True
doc.EndUndo() # End undo block.
c4d.EventAdd() # Update C4D to see changes.
gui.MessageDialog(str(num_splines_changed) + ' splines changed')
if __name__=='__main__':
main()
See Also
Code license | For all of the code on my site... if there are specific instruction or licence comments please leave them in. If you copy my code with minimum modifications to another webpage, or into any code other people will see I would love an acknowledgment to my site.... otherwise, the license for this code is more-or-less WTFPL (do what you want)! If only copying <20 lines, then don't bother. That said - if you'd like to add a web-link to my site www.andrewnoske.com or (better yet) the specific page with code, that's a really sweet gestures! Links to the page may be useful to yourself or your users and helps increase traffic to my site. Hope my code is useful! :)
|