detail_lib.py
detail_lib.py is a file that is maintained with the detailing template files that are used when you auto Detail using templates . |
Example showing the first lines of the detail_lib.py file:
import math
import model
from detailing_vars import *
from string import split
def feq(value1, value2, tol):
return math.fabs( value1 - value2 ) < tol
def fne(value1, value2, tol):
return math.fabs( value1 - value2 ) > tol
def flt(value1, value2, tol):
return (value2 - value1) > tol
def fgt(value1, value2, tol):
return (value1 - value2) > tol
def fle(value1, value2, tol):
return (value1 - value2) < tol
def fge(value1, value2, tol):
return (value2 - value1) < tol
def dist_squared (loc1, loc2):
xdist = loc1.x - loc2.x
ydist = loc1.y - loc2.y
return xdist * xdist + ydist * ydist
The
detail_lib.py
file defines functions that are useful for defining the rules in templates. It also imports useful Python modules such as the model module, which means that importing the detail_lib.py file also imports the model module. Template developers can modify the file to import additional modules and to write their own functions.
Complete contents of the detail_lib.py file (6/21/2013)
import math
import model
from detailing_vars import *
from string import split
def feq(value1, value2, tol):
return math.fabs( value1 - value2 ) < tol
def fne(value1, value2, tol):
return math.fabs( value1 - value2 ) > tol
def flt(value1, value2, tol):
return (value2 - value1) > tol
def fgt(value1, value2, tol):
return (value1 - value2) > tol
def fle(value1, value2, tol):
return (value1 - value2) < tol
def fge(value1, value2, tol):
return (value2 - value1) < tol
def dist_squared (loc1, loc2):
xdist = loc1.x - loc2.x
ydist = loc1.y - loc2.y
return xdist * xdist + ydist * ydist
def column_face_direction(job_north, column_rot):
test_rot = job_north - column_rot
PI_8 = 180 / 8.0
pi_01_8 = 1.0 * PI_8
pi_03_8 = 3.0 * PI_8
pi_05_8 = 5.0 * PI_8
pi_07_8 = 7.0 * PI_8
pi_09_8 = 9.0 * PI_8
pi_11_8 = 11.0 * PI_8
pi_13_8 = 13.0 * PI_8
pi_15_8 = 15.0 * PI_8
if fgt( test_rot, pi_01_8, 0.001 ) and fle( test_rot, pi_03_8, 0.001 ):
return "\"NORTHWEST\""
elif fgt( test_rot, pi_03_8, 0.001 ) and fle( test_rot, pi_05_8, 0.001 ):
return "\"NORTH\""
elif fgt( test_rot, pi_05_8, 0.001 ) and fle( test_rot, pi_07_8, 0.001 ):
return "\"NORTHEAST\""
elif fgt( test_rot, pi_07_8, 0.001 ) and fle( test_rot, pi_09_8, 0.001 ):
return "\"EAST\""
elif fgt( test_rot, pi_09_8, 0.001 ) and fle( test_rot, pi_11_8, 0.001 ):
return "\"SOUTHEAST\""
elif fgt( test_rot, pi_11_8, 0.001 ) and fle( test_rot, pi_13_8, 0.001 ):
return "\"SOUTH\""
elif fgt( test_rot, pi_13_8, 0.001 ) and fle( test_rot, pi_15_8, 0.001 ):
return "\"SOUTHWEST\""
elif fgt( test_rot, -pi_15_8, 0.001 ) and fle( test_rot, -pi_13_8, 0.001 ):
return "\"NORTHWEST\""
elif fgt( test_rot, -pi_13_8, 0.001 ) and fle( test_rot, -pi_11_8, 0.001 ):
return "\"NORTH\""
elif fgt( test_rot, -pi_11_8, 0.001 ) and fle( test_rot, -pi_09_8, 0.001 ):
return "\"NORTHEAST\""
elif fgt( test_rot, -pi_09_8, 0.001 ) and fle( test_rot, -pi_07_8, 0.001 ):
return "\"EAST\""
elif fgt( test_rot, -pi_07_8, 0.001 ) and fle( test_rot, -pi_05_8, 0.001 ):
return "\"SOUTHEAST\""
elif fgt( test_rot, -pi_05_8, 0.001 ) and fle( test_rot, -pi_03_8, 0.001 ):
return "\"SOUTH\""
elif fgt( test_rot, -pi_03_8, 0.001 ) and fle( test_rot, -pi_01_8, 0.001 ):
return "\"SOUTHWEST\""
else:
return "\"WEST\""
def column_end_cut_text(member_end):
if member_end.end_cut == "Square Cut":
return "SQ CUT"
elif member_end.end_cut == "Bevel Cut":
return "BEV CUT"
elif member_end.end_cut == "Mill Cut":
return "MILL CUT"
else:
return ""
def shop_callout_text(shop_bolt_types, member_qty):
if len(shop_bolt_types) == 0:
return ""
# pbt = primary bolt type
pbt = shop_bolt_types[0]
if len(shop_bolt_types) > 1:
ret_text = "SHOP BOLTS (Unless Noted):"
else:
ret_text = "SHOP BOLTS:"
bdia = to_dim(pbt.Diameter)
blen = to_dim(pbt.Length)
# once bqty is initted, it will only change never .. or .. once
bqty = pbt.Quantity
if member_qty == "ONE":
mqty = 1
else:
mqty = int(member_qty)
fmt_option = get_drawing_setup_option("sum_bolt_format")
if fmt_option == "TotalOnly":
bqty *= mqty
ret_text += "\n %i-%sx%s %s" % (bqty, bdia, blen, pbt.Name)
if pbt.Remarks != "":
ret_text += " w/%s" % pbt.Remarks
if fmt_option == "UnitAndTotal":
bqty *= mqty
ret_text += " [total=%i]" % bqty
return ret_text
def field_callout_text(field_bolt_types, member_qty):
if len(field_bolt_types) == 0:
return ""
ret_text = "FIELD BOLTS:"
if member_qty == "ONE":
mqty = 1
else:
mqty = int(member_qty)
for bolt in field_bolt_types:
bdia = to_dim(bolt.Diameter)
blen = to_dim(bolt.Length)
# once bqty is initted, it will only change never .. or .. once
bqty = bolt.Quantity
fmt_option = get_drawing_setup_option("sum_bolt_format")
if fmt_option == "TotalOnly":
bqty *= mqty
ret_text += "\n %i-%sx%s %s" % (bqty, bdia, blen, bolt.Name)
if bolt.Remarks != "":
ret_text += " w/%s" % bolt.Remarks
if fmt_option == "UnitAndTotal":
bqty *= mqty
ret_text += " [total=%i]" % bqty
return ret_text
def double_material_count(mem):
if mem.main_mtrl().MaterialType in ["Angle", "Channel"]:
if hasattr( mem, "is_double" ) and mem.is_double == "Yes":
return 2
else:
return 1
else:
return 1
def show_toe_direction_ns_note(mem):
if mem.type == "Horizontal Brc":
if mem.main_mtrl().MaterialType in ["S Tee", "W Tee"]:
return False
if mem.main_mtrl().MaterialType in ["Angle", "Channel"]:
if hasattr( mem, "is_double" ) and mem.is_double == "Yes":
return False
if hasattr( mem, "toeio" ) and mem.toeio == "In":
return True
return False
def show_toe_direction_fs_note(mem):
if mem.type == "Horizontal Brc":
if mem.main_mtrl().MaterialType in ["S Tee", "W Tee"]:
return False
if mem.main_mtrl().MaterialType in ["Angle", "Channel"]:
if hasattr( mem, "is_double" ) and mem.is_double == "Yes":
return False
if hasattr( mem, "toeio" ) and mem.toeio == "Out":
return True
return False
def show_stem_direction_ns_note(mem):
if mem.type == "Horizontal Brc":
if mem.main_mtrl().MaterialType in ["S Tee", "W Tee"]:
if hasattr( mem, "toeio" ) and mem.toeio == "In":
return True
return False
def show_stem_direction_fs_note(mem):
if mem.type == "Horizontal Brc":
if mem.main_mtrl().MaterialType in ["S Tee", "W Tee"]:
if hasattr( mem, "toeio" ) and mem.toeio == "Out":
return True
return False
def show_long_leg_note(mem):
if mem.main_mtrl().MaterialType != "Angle":
return False
if not hasattr( mem, "Depth" ):
return False
if not hasattr( mem, "FlangeWidth" ):
return False
if feq(mem.Depth, mem.FlangeWidth, 0.01):
return False
if mem.type == "Horizontal Brc" or mem.type == "Vertical Brace":
if hasattr( mem, "LLV" ) and mem.LLV == "To gusset":
return True
return False
def show_short_leg_note(mem):
if mem.main_mtrl().MaterialType != "Angle":
return False
if not hasattr( mem, "Depth" ):
return False
if not hasattr( mem, "FlangeWidth" ):
return False
if feq(mem.Depth, mem.FlangeWidth, 0.01):
return False
if mem.type == "Horizontal Brc" or mem.type == "Vertical Brace":
if hasattr( mem, "LLV" ) and mem.LLV == "Outstanding":
return True
return False
def shop_note_text(mem):
toe_ns = show_toe_direction_ns_note(mem)
toe_fs = show_toe_direction_fs_note(mem)
stem_ns = show_stem_direction_ns_note(mem)
stem_fs = show_stem_direction_fs_note(mem)
long_leg = show_long_leg_note(mem)
short_leg = show_short_leg_note(mem)
if toe_ns or toe_fs or stem_ns or stem_fs or long_leg or short_leg:
result = "SHOP NOTE:"
if toe_ns:
result += "\n TOE DIRECTION NEAR SIDE"
if toe_fs:
result += "\n TOE DIRECTION FAR SIDE"
if stem_ns:
result += "\n STEM NEAR SIDE"
if stem_fs:
result += "\n STEM FAR SIDE"
if long_leg:
result += "\n LONG LEG SHOWN"
if short_leg:
result += "\n SHORT LEG SHOWN"
return result
else:
return ""
#This is hardcoded here, because it was hardcoded in SDS2 in mtrl_pcmk.c.
#This should actually be pulled from the "Member and Material Piecemarking"
#in setup
def get_material_desc(material):
material_type = material.MaterialType
if material_type == "Channel":
return "C"
elif material_type == "Angle":
return "L"
elif material_type == "Pipe":
return "PIPE"
elif material_type == "W Tee":
return "WT"
elif material_type == "Tube":
return "TS"
elif material_type == "W flange":
return "WF"
elif material_type == "S Shape":
return "S"
elif material_type == "S Tee":
return "ST"
elif material_type == "Round plate":
return "RND PL"
elif material_type == "Round bar":
return "RND BAR"
elif material_type == "Flat bar":
return "FL"
elif material_type == "Beaded Flat":
return "HB"
elif material_type == "Flat pl layout" or material_type == "Plate":
if is_bar_stock(material):
return "FL"
else:
return "PL"
elif material_type == "Bent pl layout" or material_type == "Bent plate":
return "PL"
return ""
def get_memb_hsym_note(mem,material):
mtype = material.MaterialType
if mtype in ["Plate", "Round plate", "Rolled plate", "Bent plate"]:
return "IN PLATE"
elif mtype in ["Flat pl layout", "Bent pl layout"]:
return "IN PLATE"
elif mtype in ["Round bar", "Square bar", "Flat bar"]:
return "IN BAR"
elif mtype in ["W Tee", "S Tee"]:
return "IN TEE"
if mtype == "Angle":
return "IN ANGLE"
elif mtype == "Tube":
return "IN TUBE"
if material.MainMaterial == "Yes":
if mem.type == "Beam":
return "IN BEAM"
elif mem.type == "Column":
return "IN COLUMN"
elif mem.type in ["Horizontal brace", "Vertical brace"]:
return "IN BRACE"
return ""
def find_hole_in_group(member_number, mat_num, group_num):
for hole in model.member(member_number).materials[int(mat_num)].hole:
if hole.group == int(group_num):
return hole
return None
def is_memb_non_std_hole(memno, group):
matno, grpno = group.split("-")
hole = find_hole_in_group(memno,matno,grpno)
if hole == None:
return False
gidx = int(grpno)
sidx = int(model.member(memno).materials[int(matno)].sub_mtrl_idx)
if hole.group == gidx:
return is_nonstd_hole(sidx,gidx)
return False
def get_clip_angle_callout(material, side):
if side != "NS/FS" and side != "NS" and side != "FS":
side = extract_ns_fs(side)
mat_desc = get_material_desc(material)
text = "1-%s %s %s" % (mat_desc, material.MinorMark, side)
return text
def left_end(left,right,loc):
mid = left + (right - left) / 2.0
return flt(loc,mid,0.001)
def right_end(left,right,loc):
mid = left + (right - left) / 2.0
return fgt(loc,mid,0.001)
def extract_ns_fs(usage):
return usage[-2:]
def under_reqd_limit(mem,matno):
memno = mem.member_number
mt = model.member(memno).materials[int(matno)]
return pcmk_under_reqd_limit(mt.SubMaterialIndex)
def general_pcmk(memno,matno,side):
mt = model.member(memno).materials[int(matno)]
smi = mt.SubMaterialIndex
desc = subm_pcmk_prefix(smi)
# args to format_pcmk() -- gosl is meaningless for these
a1 = float(0.0)
a2 = float(0.0)
a3 = float(0.0)
a4 = float(0.0)
a5 = float(0.0)
a6 = float(0.0)
note_info = get_noted_shop_bolts_on_material(model.member(memno),int(matno))
note_str = ""
for ni in note_info:
this_note = "\n%i-%sx%s %s" % (ni.Quantity, to_dim(ni.Diameter), to_dim(ni.Length), ni.Name)
note_str += this_note
if side == "NS":
if get_drawing_setup_option("ns_sub_mtrl") == "No":
# near side not indicated
side = ""
else:
if get_drawing_setup_option("fs_sub_mtrl") == "No":
# far side not indicated
side = ""
fmt = format_pcmk(smi,a1,a2,a3,a4,a5,a6,side)
if fmt == "":
return fmt
return "1-" + fmt + note_str
def unopposed_clip_angle_callout(memno,matno,ga1,ga2,gol1,gol2,gosl,side):
mt = model.member(memno).materials[int(matno)]
smi = mt.SubMaterialIndex
desc = subm_pcmk_prefix(smi)
# args to format_pcmk() -- gosl is meaningless for these
# args to format_pcmk() -- ga1, ga2 gage is not needed for unopposed clip angles
a1 = float(0.0)
a2 = float(0.0)
a3 = float(gol1)
a4 = float(gol2)
a5 = float(0.0)
a6 = float(0.0)
#bolts not needed for unopposed clip angles
# note_info = get_noted_shop_bolts_on_material(model.member(memno),int(matno))
note_str = ""
# for ni in note_info:
# this_note = "\n%i-%sx%s %s" % (ni.Quantity, to_dim(ni.Diameter), to_dim(ni.Length), #ni.Name)
this_note = ""
note_str += this_note
if side == "NS":
if get_drawing_setup_option("ns_sub_mtrl") == "No":
# near side not indicated
side = ""
else:
if get_drawing_setup_option("fs_sub_mtrl") == "No":
# far side not indicated
side = ""
fmt = format_pcmk(smi,a1,a2,a3,a4,a5,a6,side)
if fmt == "":
return fmt
return "1-" + fmt + note_str
def pad_label(text):
# prepend and append 'pad' to each line of text
pad = " "
ret_text = ""
for line in split(text,"\n"):
if ret_text != "":
ret_text += "\n"
ret_text += pad + line + pad
return ret_text
def opposing_clip_angles(memno,matno_ns,matno_fs,ga1_1,ga2_1,gol1_1,gol2_1,gosl1,ga1_2,ga2_2,gol1_2,gol2_2,gosl2):
mt_ns = model.member(memno).materials[int(matno_ns)]
mt_fs = model.member(memno).materials[int(matno_fs)]
desc_ns = subm_pcmk_prefix(mt_ns.SubMaterialIndex)
desc_fs = subm_pcmk_prefix(mt_fs.SubMaterialIndex)
minor_ns = mt_ns.MinorMark
minor_fs = mt_fs.MinorMark
# args to format_pcmk()
a1 = float(ga1_1)
a2 = float(ga2_1)
a3 = float(gol1_1)
a4 = float(gol2_1)
a5 = float(gosl1)
a6 = float(gosl2)
# for these, we want the distance..
a5 = abs(a5 - a6)
a6 = float(0.0)
note_info = get_noted_shop_bolts_on_material(model.member(memno),int(matno_ns))
note_str = ""
for ni in note_info:
this_note = "\n%i-%sx%s %s" % (ni.Quantity, to_dim(ni.Diameter), to_dim(ni.Length), ni.Name)
note_str += this_note
smi = mt_ns.SubMaterialIndex
if desc_ns == desc_fs and minor_ns == minor_fs:
if get_drawing_setup_option("ns_fs_sub_mtrl") == "Yes":
# both sides indicated
fmt = format_pcmk(smi,a1,a2,a3,a4,a5,a6,"NS/FS")
if fmt == "":
return fmt
ret_text = "1-" + fmt + note_str
return pad_label(ret_text)
opt_ns = get_drawing_setup_option("ns_sub_mtrl")
opt_fs = get_drawing_setup_option("fs_sub_mtrl")
if opt_ns == "No" and opt_fs == "No":
# neither side indicated
fmt = format_pcmk(smi,a1,a2,a3,a4,a5,a6,"")
if fmt == "":
return fmt
ret_text = "2-" + fmt + note_str
return pad_label(ret_text)
side = ""
if get_drawing_setup_option("ns_sub_mtrl") == "Yes":
# near side indicated
side = "NS"
fmt = format_pcmk(smi,a1,a2,a3,a4,a5,a6,side)
if fmt == "":
text_ns = fmt
else:
text_ns = "1-" + fmt
smi = mt_fs.SubMaterialIndex
a1 = float(ga1_2)
a2 = float(ga2_2)
a3 = float(gol1_2)
a4 = float(gol2_2)
side = ""
if get_drawing_setup_option("fs_sub_mtrl") == "Yes":
# far side indicated
side = "FS"
fmt = format_pcmk(smi,a1,a2,a3,a4,a5,a6,side)
if fmt == "":
text_fs = fmt
else:
text_fs = "1-" + fmt
ns_list = split(text_ns,"\n")
fs_list = split(text_fs,"\n")
ii = 0
for line_ns in ns_list:
if ii == 0:
text_ns = line_ns
else:
found = False
for line_fs in fs_list:
if line_ns == line_fs:
found = True
break
if found == False:
text_ns += "\n"
text_ns += line_ns
ii += 1
# at this point, we need a multilabel
text = "%s\n%s" % (text_ns, text_fs)
ret_text = text + note_str
return pad_label(ret_text)
def cmp_dia_slen_rot(d1,s1,r1,d2,s2,r2):
if fne( float(d1), float(d2), 0.001 ):
return False
if fne( float(s1), float(s2), 0.001 ):
return False
ar1 = math.fabs( float(r1 ) )
ar2 = math.fabs( float(r2 ) )
if( fne( ar1, ar2, 0.001 ) ):
return False
return True
def hsym_lox(d1,s1,r1,t1,h1,n1):
return get_hsym_lox(float(d1),float(s1),float(r1),t1,int(h1),bool(n1))
def hsym_loy(d1,s1,r1,t1,h1,n1):
return get_hsym_loy(float(d1),float(s1),float(r1),t1,int(h1),bool(n1))
def hsym_hix(d1,s1,r1,t1,h1,n1):
return get_hsym_hix(float(d1),float(s1),float(r1),t1,int(h1),bool(n1))
def hsym_hiy(d1,s1,r1,t1,h1,n1):
return get_hsym_hiy(float(d1),float(s1),float(r1),t1,int(h1),bool(n1))
def hsym_dx(d1,s1,r1,t1,h1,n1):
if feq( float(s1), 0.0, 0.03 ):
# regular holes work correctly without intervention
return 0.0
rot = math.fabs(float(r1))
if flt( rot, 25.0, 0.001 ):
if h1 < 0:
return hsym_hix(d1,s1,r1,t1,h1,n1)
return hsym_lox(d1,s1,r1,t1,h1,n1)
if flt( rot, 75.0, 0.001 ):
lox = hsym_lox(d1,s1,r1,t1,h1,n1)
hix = hsym_hix(d1,s1,r1,t1,h1,n1)
return lox + (hix - lox) / 2.0
if h1 < 0:
return hsym_lox(d1,s1,r1,t1,h1,n1)
return hsym_hix(d1,s1,r1,t1,h1,n1)
def hsym_dy(d1,s1,r1,t1,h1,n1):
return hsym_hiy(d1,s1,r1,t1,h1,n1)
def sec_hsym_dy(d1,s1,r1,d2,s2,r2,t1,handle,no_text_rot):
prev_ymax = hsym_hiy(d1,s1,r1,t1,handle,no_text_rot)
prev_ymin = hsym_loy(d1,s1,r1,t1,handle,no_text_rot)
this_ymax = hsym_hiy(d2,s2,r2,"",handle,no_text_rot)
return (prev_ymax - prev_ymin) + this_ymax
def pad_hsym_dx(d1,s1,r1,t1,h1,n1):
padding = len(pad_label("")) / 2
return hsym_dx(d1,s1,r1,t1,h1,n1) + (h1 * padding)
def ns_fs_plate_callout_text(mem, material, material_num, usage):
if usage == "Brace Web Fill Plate":
return "1-%s %s\nFILL PLATE\nNS/FS" % (get_material_desc(material), material.MinorMark)
else:
bolt_info = get_shop_bolts_on_material(mem, int(material_num) )[0];
return "1-%s %s NS/FS\n%i-%sx%s %s" % (get_material_desc(material), material.MinorMark, bolt_info.Quantity/2, to_dim(bolt_info.Length), to_dim(bolt_info.Diameter), bolt_info.Name)
#Sort the holes by group and material
def sort_by_group(match_dict, match, var_name):
val = match.vars[var_name]
if not match_dict.has_key(val):
match_dict[val] = []
match_dict[val].append(match)
#Goes through a group of matches and returns those that
#maximize the expression
def get_minimums(group, expression):
group.sort(lambda m1, m2: cmp(expression(m1), expression(m2)))
min_val = expression(group[0]) + .001
return filter(lambda m: expression(m) < min_val, group)
def get_hgrp_minimums(group, expression):
return get_minimums(group, expression)
def get_hole_minimums(group, expression):
group.sort(lambda m1, m2: cmp((m1.locations[0].props["Hole Group"],expression(m1)),(m2.locations[1].props["Hole Group"],expression(m2))))
min_val = expression(group[0]) + .001
return filter(lambda m: expression(m) < min_val, group)
def group_by_variable(matches, var_name):
match_dict = {}
for match in matches:
sort_by_group(match_dict, match, var_name)
return match_dict.values()
# Groups a list of detail rule matches by a specified property value at a
# specified location. Matches are also separated by View Number so matches
# in different views that would otherwise be grouped together will be in
# separate groups.
# matches: the list of matches.
# locationIdx: the index of the location in the match's locations list.
# propName: the name of the property to group by.
# Returns a dictionary where the key is the group's property value and view
# number and the value is the list of detail rule matches that have that
# property value. Any matches that do not have the specified property will
# be placed in a group with the view number as the key.
def group_by_prop(matches, locationIdx, propName):
groups = {}
for match in matches:
location = match.locations[locationIdx]
viewNum = location.props["View Number"]
if location.props.has_key(propName):
propVal = location.props[propName] + ", " + viewNum
else:
propVal = viewNum
if not groups.has_key(propVal):
groups[propVal] = []
groups[propVal].append(match)
return groups
# Groups a list of detail rule matches by a specified variable value.
# matches: the list of matches.
# varName: the name of the variable by which to group the matches.
# Returns a dictionary where the key is the group's variable value and
# the dictionary value is the list of matches for that group.
def group_by_var(matches, varName):
groups = {}
for match in matches:
var = ""
if match.vars.has_key(varName):
var = match.vars[varName]
if not groups.has_key(var):
groups[var] = []
groups[var].append( match )
return groups
def minimize_expression(match_groups, expression):
matches = []
for group in match_groups:
matches += get_minimums(group, expression)
return matches
def minimize_hgrp_expression(match_groups, expression):
matches = []
for group in match_groups:
matches += get_hgrp_minimums(group, expression)
return matches
def minimize_hole_expression(match_groups, expression):
matches = []
for group in match_groups:
matches += get_hole_minimums(group, expression)
return matches
def get_maximums(group,expression):
group.sort(lambda m1, m2: cmp(expression(m1), expression(m2)))
max_val = expression(group[0]) - .001
return filter(lambda m: expression(m) > max_val, group)
def get_hgrp_maximums(group, expression):
return get_maximums(group, expression)
def get_hole_maximums(group, expression):
group.sort(lambda m1, m2: cmp((m1.locations[0].props["Hole Group"],expression(m1)),(m2.locations[1].props["Hole Group"],expression(m2))))
max_val = expression(group[0]) - .001
return filter(lambda m: expression(m) < max_val, group)
def maximize_expression(match_groups, expression):
matches = []
for group in match_groups:
matches += get_maximums(group, expression)
return matches
def maximize_hgrp_expression(match_groups, expression):
matches = []
for group in match_groups:
matches += get_hgrp_maximums(group, expression)
return matches
def maximize_hole_expression(match_groups, expression):
matches = []
for group in match_groups:
matches += get_hole_maximums(group, expression)
return matches
# used in python:filter -- functionality repeated frequently. useful
# for chaining dimensions with constraint 'running dimension' (which
# i think is poorly named -- 'running' says 'extension' to me).
#
# it keeps the locations-of-interest weeded-down to successive points.
# for example, say that we have 3 points 'a', 'b' and 'c'. if we don't
# use this type of thing, then we get matches for (a-b), (a-c) and (b-c)
# instead of just (a-b) and (b-c)
#
# sample usage:
# from detail_lib import *
# matches = filter_min_hgrp(matches,"HoleGroup1")
#
# .. and having additional python:condition akin to
# HoleGroup1 != HoleGroup2
#
def filter_min_hgrp(matches,var):
match_groups = group_by_variable(matches,var)
return minimize_hgrp_expression(match_groups,lambda m1: dist_squared(m1.locations[0], m1.locations[1]))
# sample usage:
# from detail_lib import *
# matches = filter_min_hole(matches,"HoleCol1")
#
# .. and having additional python:condition akin to
# HoleCol1 != HoleCol2
#
def filter_min_hole(matches,var):
match_groups = group_by_variable(matches,var)
return minimize_hole_expression(match_groups,lambda m1: dist_squared(m1.locations[0], m1.locations[1]))
class location:
def __init__(self, x, y):
self.x = x
self.y = y
def dot(v1, v2):
return v1.x * v2.x + v1.y * v2.y
def length(v):
return math.sqrt(v.x * v.x + v.y * v.y)
def normalize(v):
mag = length(v)
return location(v.x / mag, v.y / mag)
def angle_between(v1, v2):
return math.acos( dot( v1, v2 ) )
def is_point_between(end1, end2, between):
v1_2 = normalize(location(end2.x - end1.x, end2.y - end1.y))
v2_1 = location(-v1_2.x, -v1_2.y)
v1_b = normalize(location(between.x - end1.x, between.y - end1.y))
v2_b = normalize(location(between.x - end2.x, between.y - end2.y))
return ((angle_between(v1_2, v1_b) < math.pi / 2.0) and
(angle_between(v2_1, v2_b) < math.pi / 2.0))
# Groups a list of matches by a specified location into a dictionary.
# matchList: the list of matches
# locationIdx: the locations array index of the location to group by.
# returns a dictionary where the key is a string of the location's x and y
# coordinates separated by a comma, and the value is the list of matches
# grouped by that location.
def group_by_location( matchList, locationIdx ):
groups = {}
for match in matchList:
loc = match.locations[int(locationIdx)]
mtrlNum = ""
if loc.props.has_key("Material Number"):
mtrlNum = loc.props["Material Number"]
holeGroup = ""
if loc.props.has_key("Hole Group"):
holeGroup = loc.props["Hole Group"]
key = str(loc.x) + "," + str(loc.y) + "_" + mtrlNum + "_" + holeGroup
if not groups.has_key( key ):
groups[key] = []
groups[key].append( match )
return groups
# Groups matches whose anchors are colinear.
# matches: the list of matches.
# anchor1Idx: the index of the first anchor point.
# anchor2Idx: the index of the second anchor point.
# rotationVar: the variable name for the annotation direction.
def group_by_anchors( matches, anchor1Idx, anchor2Idx, rotationVar ):
groups = {}
for m in matches:
found = False
rotation = float( m.vars[rotationVar] )
for key in groups:
oDist1 = ortho_dist(key.locations[anchor1Idx], m.locations[anchor1Idx], rotation)
oDist2 = ortho_dist(key.locations[anchor2Idx], m.locations[anchor2Idx], rotation)
if feq(oDist1, 0.0, 0.0001) and feq(oDist2, 0.0, 0.0001):
found = True
groups[key].append(m)
if not found:
groups[m] = []
groups[m].append(m)
return groups
# Calculates the squared distance between two locations in a match.
# match: the rule match.
# loc1Idx: the locations array index of the first location.
# loc2Idx: the locations array index of the second location.
# returns the squared distance between the two locations.
def location_dist_sq( match, loc1Idx, loc2Idx ):
return dist_squared( match.locations[int(loc1Idx)], match.locations[int(loc2Idx)] )
# Finds the items in a list of matches that have the shortest distance between two
# specified locations.
# matchList: the list of matches.
# loc1Idx: the locations array index of the first location.
# loc2Idx: the locations array index of the second location.
# returns the match(es) that minimize the distance between the specified locations.
def filter_by_min_distance( matchList, loc1Idx, loc2Idx ):
matchList.sort(lambda m1, m2: cmp(location_dist_sq(m1, int(loc1Idx), int(loc2Idx)), location_dist_sq(m2, int(loc1Idx), int(loc2Idx))) )
minDist = location_dist_sq(matchList[0], int(loc1Idx), int(loc2Idx))
return filter(lambda m: feq(minDist, location_dist_sq(m, int(loc1Idx), int(loc2Idx)), 0.0000001), matchList)
# Finds the items in a list of matches that have the greatest distance between two
# specified locations.
# matchList: the list of matches.
# loc1Idx: the locations array index of the first location.
# loc2Idx: the locations array index of the second location.
# returns the match(es) that maximize the distance between the specified locations.
def filter_by_max_distance( matchList, loc1Idx, loc2Idx ):
matchList.sort(lambda m1, m2: -cmp(location_dist_sq(m1, int(loc1Idx), int(loc2Idx)), location_dist_sq(m2, int(loc1Idx), int(loc2Idx))) )
maxDist = location_dist_sq(matchList[0], int(loc1Idx), int(loc2Idx))
return filter(lambda m: feq(maxDist, location_dist_sq(m, int(loc1Idx), int(loc2Idx)), 0.0000001), matchList)
# Gets the squared distance between two points as specified by two x/y coordinate
# floating point pairs.
# x1: the x coordinate for the first point.
# y1: the y coordinate for the first point.
# x2: the x coordinate for the second point.
# y2: the y coordinate for the second point.
def sq_dist( x1, y1, x2, y2 ):
dx = x2 - x1
dy = y2 - y1
return math.sqrt(dx * dx + dy * dy)
# Filter that limits matches to those that are furthest from or nearest to the annotation.
# This is a private procedure that can be accessed through filter_to_furthest_hole_row_or_col(...)
# and filter_to_nearest_hole_row_or_col(...)
#
# matches: list of all candidate matches.
# groupByLoc: a location which is the same for all matches for a single
# annotation (e.g. hole group reference, material anchor).
# holeLoc: a named location to a hole that is annotated.
# rotationVar: the name of the variable that gives the annotation rotation
# (e.g. provided by a Named Rotation constraint).
# column: True to filter to the furthest/nearest column and false to filter to the furthest/nearest row.
# furthest: True to filter to the furthest match and False to filter to the nearest match.
# Returns the filtered list of matches.
def __filter_to_hole_row_or_col( matches, groupByLoc, holeLoc, rotationVar, column, furthest ):
groups = group_by_location( matches, groupByLoc )
matches = []
for key in groups:
g = groups[key]
angle = math.radians( float( g[0].vars[rotationVar] ) + 90.0 )
x = g[0].locations[holeLoc].x + math.cos(angle) * 999.0
y = g[0].locations[holeLoc].y + math.sin(angle) * 999.0
initializeDist = 0.0 if furthest else 9999999.0
bestDist = initializeDist
bestGrp = []
holeProp = "Hole Col" if column else "Hole Row"
if not g[0].locations[holeLoc].props.has_key(holeProp):
holeProp = "Stagger " + holeProp
holePropGrps = group_by_prop(g, holeLoc, holeProp)
for cGKey in holePropGrps:
colG = holePropGrps[cGKey]
dist = initializeDist
for m in colG:
loc = m.locations[holeLoc]
d = sq_dist( x, y, loc.x, loc.y )
if (furthest and d > dist) or ((not furthest) and d < dist):
dist = d
if (furthest and dist > bestDist) or ((not furthest) and dist < bestDist):
bestDist = dist
bestGrp = colG
matches.extend( bestGrp )
return matches
# Filter that limits matches to those that are furthest from the annotation.
#
# For a given hole group we ususally want the annotation lines to go through all
# rows/columns in the hole group. This filter groups all candidate matches for
# each annotation and removes any that don't dimension to the furthest row/column.
# matches: list of all candidate matches.
# groupByLoc: a location which is the same for all matches for a single
# annotation (e.g. hole group reference, material anchor).
# holeLoc: a named location to a hole that is annotated.
# rotationVar: the name of the variable that gives the annotation rotation
# (e.g. provided by a Named Rotation constraint).
# column: True to filter to the furthest column and false to filter to the furthest row.
# Returns the filtered list of matches.
def filter_to_furthest_hole_row_or_col( matches, groupByLoc, holeLoc, rotationVar, column ):
return __filter_to_hole_row_or_col( matches, groupByLoc, holeLoc, rotationVar, column, True )
# Filter that limits matches to those that are nearest to the annotation.
#
# This filter groups all candidate matches for each annotation and removes any that
# don't dimension to the nearest row/column.
# matches: list of all candidate matches.
# groupByLoc: a location which is the same for all matches for a single
# annotation (e.g. hole group reference, material anchor).
# holeLoc: a named location to a hole that is annotated.
# rotationVar: the name of the variable that gives the annotation rotation
# (e.g. provided by a Named Rotation constraint).
# column: True to filter to the nearest column and false to filter to the nearest row.
# Returns the filtered list of matches.
def filter_to_nearest_hole_row_or_col( matches, groupByLoc, holeLoc, rotationVar, column ):
return __filter_to_hole_row_or_col( matches, groupByLoc, holeLoc, rotationVar, column, False )
# Filter that limits matches to those that are furthest from or nearest to the annotation.
# This is a private procedure that can be accessed through filter_to_nearest_location(...)
# and filter_to_furthest_location(...)
#
# matches: list of all candidate matches.
# groupByLoc: a location which is the same for all matches for a single
# annotation (e.g. hole group reference, material anchor).
# loc: the location to which the furthest/nearest to annotation filter is applied.
# rotationVar: the name of the variable that gives the annotation rotation
# (e.g. provided by a Named Rotation constraint).
# furthest: True to filter to the furthest match and False to filter to the nearest match.
# Returns the filtered list of matches.
def __filter_to_location( matches, groupByLoc, loc, rotationVar, furthest ):
groups = group_by_location( matches, groupByLoc )
matches = []
for key in groups:
g = groups[key]
angle = math.radians( float( g[0].vars[rotationVar] ) + 90.0 )
x = g[0].locations[loc].x + math.cos(angle) * 999.0
y = g[0].locations[loc].y + math.sin(angle) * 999.0
initializeDist = 0.0 if furthest else 9999999.0
bestDist = initializeDist
bestGrp = []
locGrps = group_by_location(g, loc )
for lKey in locGrps:
lGrp = locGrps[lKey]
dist = initializeDist
for m in lGrp:
l = m.locations[loc]
d = sq_dist( x, y, l.x, l.y )
if (furthest and d > dist) or ((not furthest) and d < dist):
dist = d
if (furthest and dist > bestDist) or ((not furthest) and dist < bestDist):
bestDist = dist
bestGrp = lGrp
matches.extend( bestGrp )
return matches
# Filter that limits matches to those that are nearest to the annotation.
#
# matches: list of all candidate matches.
# groupByLoc: a location which is the same for all matches for a single
# annotation (e.g. hole group reference, material anchor).
# loc: the location to which the nearest to annotation filter is applied.
# rotationVar: the name of the variable that gives the annotation rotation
# (e.g. provided by a Named Rotation constraint).
# Returns the filtered list of matches.
def filter_to_nearest_location( matches, groupByLoc, loc, rotationVar ):
return __filter_to_location( matches, groupByLoc, loc, rotationVar, False )
# Filter that limits matches to those that are furthest from the annotation.
#
# matches: list of all candidate matches.
# groupByLoc: a location which is the same for all matches for a single
# annotation (e.g. hole group reference, material anchor).
# loc: the location to which the furthest from annotation filter is applied.
# rotationVar: the name of the variable that gives the annotation rotation
# (e.g. provided by a Named Rotation constraint).
# Returns the filtered list of matches.
def filter_to_furthest_location( matches, groupByLoc, loc, rotationVar ):
return __filter_to_location( matches, groupByLoc, loc, rotationVar, True )
# Gives the dot product of two vectors
# vec1: the first vector.
# vec2: the second vector.
# Returns the dot product.
def dot_prod( vec1, vec2 ):
return sum(p*q for p,q in zip(vec1, vec2))
# Gives the distance orthogonal (normal/perpendicular) to a specified direction between two points.
# l1: the first location point.
# l2: the second location point.
# rotation: the direciton.
# Returns the orthogonal distance.
def ortho_dist( l1, l2, rotation ):
angle = math.radians( float(rotation) + 90.0 )
h = math.sqrt( dist_squared( l1, l2 ) )
vec1 = (math.cos(angle), math.sin(angle))
vec2 = (l2.x - l1.x, l2.y - l1.y)
v2Mag = math.sqrt(dot_prod(vec2,vec2))
if feq( v2Mag, 0.0, 0.0001 ):
return 0.0
vec2 = (vec2[0] / v2Mag, vec2[1] / v2Mag)
dotV1_V2 = dot_prod( vec1, vec2 )
if feq( dotV1_V2, 0.0, 0.0001 ):
return h
# The result of the dot product is clamped here to prevent 'math domain errors' caused
# by truncation errors causing the value to be outside of [-1.0,1.0].
dotAngle = math.acos( min(1.0, max(dotV1_V2, -1.0) ) )
return h * math.sin(dotAngle)
# Gives the distance orthogonal (normal/perpendicular) to a specified direction between two points.
# match: the rule match that is being tested.
# loc1Idx: the index of the first point.
# loc2Idx: the index of the second point.
# rotationVar: the variable name for the direciton.
# Returns the orthogonal distance.
def orthogonal_distance( match, loc1Idx, loc2Idx, rotationVar ):
l1 = match.locations[loc1Idx]
l2 = match.locations[loc2Idx]
return ortho_dist(l1, l2, float(match.vars[rotationVar]))
# Filter that limits matches to those with the minimum distance orthogonal to a
# specified direction between two points.
# matchList: the list of candidate rule matches.
# loc1Idx: the index of the first point.
# loc2Idx: the index of the second point.
# rotationVar: the variable name for the direciton.
# Returns the matches with the minimum orthogonal distance.
def filter_by_min_orthogonal_distance( matchList, loc1Idx, loc2Idx, rotationVar ):
matchList.sort(lambda m1, m2: cmp(orthogonal_distance(m1, int(loc1Idx), int(loc2Idx), rotationVar), orthogonal_distance(m2, int(loc1Idx), int(loc2Idx), rotationVar)) )
minDist = orthogonal_distance(matchList[0], int(loc1Idx), int(loc2Idx), rotationVar)
return filter(lambda m: feq(minDist, orthogonal_distance(m, int(loc1Idx), int(loc2Idx), rotationVar), 0.0001), matchList)
# Filter that limits matches to those with the maximum distance orthogonal to a
# specified direction between two points.
# matchList: the list of candidate rule matches.
# loc1Idx: the index of the first point.
# loc2Idx: the index of the second point.
# rotationVar: the variable name for the direciton.
# Returns the matches with the maximum orthogonal distance.
def filter_by_max_orthogonal_distance( matchList, loc1Idx, loc2Idx, rotationVar ):
matchList.sort(lambda m1, m2: -cmp(orthogonal_distance(m1, int(loc1Idx), int(loc2Idx), rotationVar), orthogonal_distance(m2, int(loc1Idx), int(loc2Idx), rotationVar)) )
maxDist = orthogonal_distance(matchList[0], int(loc1Idx), int(loc2Idx), rotationVar)
return filter(lambda m: feq(maxDist, orthogonal_distance(m, int(loc1Idx), int(loc2Idx), rotationVar), 0.0001), matchList)
# Rotates a point by a specified angle about a specified pivot point.
# rotatedPt: the point that gets rotated.
# angle: the magnitude of the rotation in radians.
# pivotPt: the pivot about which the point is rotated.
# Returns the point after it has been rotated.
def rotate_location( rotatedPt, angle, pivotPt ):
pt = (rotatedPt[0] - pivotPt[0], rotatedPt[1] - pivotPt[1] )
x = pt[0] * math.cos( angle ) - pt[1] * math.sin( angle )
y = pt[0] * math.sin( angle ) + pt[1] * math.cos( angle )
return (pivotPt[0] + x, pivotPt[1] + y)
# Splits a set of rule matches into two groups where the specified location falls on either
# side of a line defined by a split point and rotation direction.
# matches: the set of rule matches to split.
# locIdx: the index of the location that is checked to determine which side of the
# split line it falls on.
# splitLocIdx: the index of the location through which the split line passes.
# rotationVar: the name of the variable that gives the rotation direction of the split line.
# Returns two lists of matches that fall on either side of the split line.
def split_locations( matches, locIdx, splitLocIdx, rotationVar ):
groups = {}
groups["high"] = []
groups["low"] = []
for m in matches:
angle = math.radians( 360.0 - (float( m.vars[rotationVar] ) + 90.0) )
loc = m.locations[locIdx]
splitLoc = m.locations[splitLocIdx]
pt = rotate_location( (loc.x, loc.y), angle, (splitLoc.x, splitLoc.y) )
if fge( pt[1], splitLoc.y, 0.0001 ):
groups["high"].append( m )
else:
groups["low"].append( m )
return groups
# Represents a two-dimensional point.
# Most procedures that take rule match location (e.g. matches[0].locations[0])
# as a parameter will also work with a Point2D and vice versa.
class Point2D:
x = 0.0 # the X coordinate
y = 0.0 # the Y coordinate
# Static factory method copy constructor for instantiating a Point2D from another
# Point2D or a rule match location.
# other: the object from which to initialize the new Point2D.
@classmethod
def Clone(cls, other):
p = cls()
p.x = other.x
p.y = other.y
return p
# Static factory method for instantiating a Point2D from x-y coordinates.
# x: the X coordinate.
# y: the Y coordinate.
@classmethod
def FromXY(cls, x, y):
p = cls()
p.x = x
p.y = y
return p
# Checks if this point's x & y-coordinates are equal to another point's.
# other: the other point.
# accuracy: the accuracy of the comparison.
def equals(self, other, accuracy = 0.0001):
return self.xeq(other, accuracy) and self.yeq(other, accuracy)
# Checks if this point's x & y-coordinates are equal to another point's.
# other: the other point.
# accuracy: the accuracy of the comparison.
def notequals(self, other, accuracy = 0.0001):
return self.xne(other, accuracy) and self.yne(other, accuracy)
# Checks if this point's x-coordinate is equal to another point's.
# other: the other point.
# accuracy: the accuracy of the comparison.
def xeq(self, other, accuracy = 0.0001):
return feq(self.x, other.x, accuracy)
# Checks if this point's x-coordinate is not equal to another point's.
# other: the other point.
# accuracy: the accuracy of the comparison.
def xne(self, other, accuracy = 0.0001):
return not self.xeq(other, accuracy)
# Checks if this point's x-coordinate is greater than another point's.
# other: the other point.
# accuracy: the accuracy of the comparison.
def xgt(self, other, accuracy = 0.0001):
return fgt(self.x, other.x, accuracy)
# Checks if this point's x-coordinate is less than another point's.
# other: the other point.
# accuracy: the accuracy of the comparison.
def xlt(self, other, accuracy = 0.0001):
return flt(self.x, other.x, accuracy)
# Checks if this point's x-coordinate is less than or equal to another point's.
# other: the other point.
# accuracy: the accuracy of the comparison.
def xle(self, other, accuracy = 0.0001):
return not self.xgt(other, accuracy)
# Checks if this point's x-coordinate is greater than or equal to another point's.
# other: the other point.
# accuracy: the accuracy of the comparison.
def xge(self, other, accuracy = 0.0001):
return not self.xlt(other, accuracy)
# Checks if this point's y-coordinate is equal to another point's.
# other: the other point.
# accuracy: the accuracy of the comparison.
def yeq(self, other, accuracy = 0.0001):
return feq(self.y, other.y, accuracy)
# Checks if this point's y-coordinate is not equal to another point's.
# other: the other point.
# accuracy: the accuracy of the comparison.
def yne(self, other, accuracy = 0.0001):
return not self.yeq(other, accuracy)
# Checks if this point's y-coordinate is greater than another point's.
# other: the other point.
# accuracy: the accuracy of the comparison.
def ygt(self, other, accuracy = 0.0001):
return fgt(self.y, other.y, accuracy)
# Checks if this point's y-coordinate is less than another point's.
# other: the other point.
# accuracy: the accuracy of the comparison.
def ylt(self, other, accuracy = 0.0001):
return flt(self.y, other.y, accuracy)
# Checks if this point's y-coordinate is less than or equal to another point's.
# other: the other point.
# accuracy: the accuracy of the comparison.
def yle(self, other, accuracy = 0.0001):
return not self.ygt(other, accuracy)
# Checks if this point's y-coordinate is greater than or equal to another point's.
# other: the other point.
# accuracy: the accuracy of the comparison.
def yge(self, other, accuracy = 0.0001):
return not self.ylt(other, accuracy)
# Gives the Euclidean distance between this and another point.
# other: the other point.
# Returns the distance.
def dist(self, other):
dX = self.x - other.x
dY = self.y - other.y
return math.sqrt( dX * dX + dY * dY )
# Gives the distance between this and another point in the x-direction.
# other: the other point.
# Returns the distance.
def xDist(self, other):
return math.fabs( self.x - other.x )
# Gives the distance between this and another point in the y-direction.
# other: the other point.
# Returns the distance.
def yDist(self, other):
return math.fabs( self.y - other.y )
# Assuming the point is a vector returns its magnitude.
# Returns: the magnitude.
def vectorMagnitude(self):
return self.dist( Point2D.FromXY( 0, 0 ) )
# Calculates the sum of two points that represent vectors.
# other: the other vector.
# Returns the vector sum.
def vectorAdd(self, other):
return Point2D.FromXY( self.x + other.x, self.y + other.y )
# Calculates the difference of two points that represent vectors.
# other: the other vector.
# Returns the vector difference.
def vectorSubtract(self, other):
return Point2D.FromXY( self.x - other.x, self.y - other.y )
# Calculates the product of this vector and a scalar factor.
# factor: the scalar factor.
# Returns the procuct.
def scalarMultiply(self, factor):
return Point2D.FromXY( self.x * factor, self.y * factor )
# Assuming the point is a vector returns the unit vector.
# Returns the unit vector.
def unit(self):
dot = self.dot(self)
if dot == 0.0:
return self
return self.scalarMultiply( 1.0 / math.sqrt(dot) )
# Calculates the dot product of this and another vector.
# other: the other vector.
# Returns the dot product.
def dot(self, other):
return self.x * other.x + self.y * other.y
# Checks if this point is colinear with two other points (i.e. they are all on the same line).
# other1: the first point.
# other2: the second point.
# Returns True if the points are colinear and false otherwise.
def IsColinear(self, other1, other2, accuracy = 0.0001):
pt1 = Point2D.Clone( other1 )
pt2 = Point2D.Clone( other2 )
if self.equals(pt1) or self.equals(pt2):
return True
v1 = pt1.vectorSubtract( self ).unit()
v2 = pt2.vectorSubtract( self ).unit()
dot = math.fabs( v1.dot(v2) )
return feq( dot, 1.0, accuracy )
# Checks if this point is colinear with another point along a line that passes through the
# point at a specified angle.
# other: the other point.
# angle: the angle of the line.
# Returns True if the points are colinear and False otherwise.
def IsColinearAngle(self, other, angle, accuracy = 0.0001 ):
o = Point2D.Clone(other)
ang = math.radians(float(angle)+90.0)
d = self.dist(o) * 2.0
ox = Point2D.FromXY(d * math.cos(ang), d * math.sin(ang))
os = o.vectorSubtract(ox)
oe = o.vectorAdd(ox)
dr = os.dist(oe)
dps = os.dist(self)
dpe = oe.dist(self)
return feq(dr, dps + dpe, accuracy)
# Rotates the point around a specified pivot by a specified angle.
# angle: the angle to rotate.
# pivot: the pivot point for the rotation.
# Returns the rotated point.
def rotate( self, angle, pivot ):
ang = math.radians(float(angle)+90.0)
pt = self.vectorSubtract(pivot)
dx = pt.x * math.cos( ang ) - pt.y * math.sin( ang )
dy = pt.x * math.sin( ang ) + pt.y * math.cos( ang )
return Point2D.FromXY(pivot.x + dx, pivot.y + dy)
# Gives the centroid of a list of points.
# locationList: the list of points for which to calculate the centroid.
# Returns the centroid.
def centroid( locationList ):
xSum = 0;
ySum = 0;
for l in locationList:
xSum += l.x
ySum += l.y
size = len( locationList )
return Point2D.FromXY( xSum / size, ySum / size )
def is_setup(key,value):
return get_drawing_setup_option(key) == value