# coding: utf-8
import apex
from apex.construct import Point3D, Point2D
import os
import sys
import time

# Bolt  Stuff

import random

from get_stuff_3 import *

apex.setScriptUnitSystem(unitSystemName = r'''mm-kg-s-N-K''')
# 
# Start of recorded operations
#

####
import cProfile,pstats, io

def createCurveFromCords(cord1,cord2):	
	#_target = [		
	#Point3D( cord1[0],  cord1[1], cord1[2] ),
	#Point3D( cord2[0],  cord2[1], cord2[2] )
	#]

	p1=apex.Coordinate( cord1[0],  cord1[1], cord1[2] )
	p2=apex.Coordinate( cord2[0],  cord2[1], cord2[2] )
	
	_target = apex.IPhysicalCollection()
	_target.append(p1)
	_target.append(p2)
	
	# will return an entity
	entity = apex.geometry.createCurve(
		target = _target,
		behavior = apex.geometry.CurveBehavior.Polyline
	)
	
	return entity.asCurve()

def ScaleBoxPoints(box,scale_fact):

	# In IL FP1 the getBondingBox returns IPhy (not 3D points)
	IPhy1=box.getPoint1()
	IPhy2=box.getPoint2()
	
	# grow bounding box
	x1=IPhy1.getX()
	y1=IPhy1.getY()
	z1=IPhy1.getZ()

	x2=IPhy2.getX()
	y2=IPhy2.getY()
	z2=IPhy2.getZ()
	
	dx=(x2-x1)*scale_fact
	dy=(y2-y1)*scale_fact
	dz=(z2-z1)*scale_fact
	
	Point1=apex.Coordinate()
	Point2=apex.Coordinate()

	Point1.setX(x1-dx)
	Point1.setY(y1-dy)
	Point1.setZ(z1-dz)
	
	Point2.setX(x2+dx)
	Point2.setY(y2+dy)
	Point2.setZ(z2+dz)
	
	if 1==0:
		x1=Point1.getX()
		y1=Point1.getY()
		z1=Point1.getZ()

		x2=Point2.getX()
		y2=Point2.getY()
		z2=Point2.getZ()
		p1_point=apex.geometry.createPointXYZ(x1,y1,z1)
		text1=apex.display.displayText("p1" + str(scale_fact),Point1)
		
		p2_point=apex.geometry.createPointXYZ(x2,y2,z2)
		text1=apex.display.displayText("p2" + str(scale_fact),Point2)
	
	
	return Point1, Point2

def CreateAssyAndPartsinAssy(parent_assy,new_assy_name,NewPartNameList=[]):
	#
	parent_assy_name=parent_assy.getPath() + "/" + parent_assy.getName()	
	
	
	new_assy_full_name=parent_assy_name + "/" + new_assy_name
	print(new_assy_full_name)
	try:
		print("I am in 1")
		new_assy = apex.getAssembly( pathName = new_assy_full_name )
		print("Assy " + new_assy_name + " already exists in assy " + parent_assy_name)
		print("I am in 2")
		
	except:
		new_assy = parent_assy.createAssembly()
		updatedAssemblyName_ = new_assy.update( name = new_assy_name )
		print("Created :" + new_assy_name)
	
	# check if assy exists
	
	
	parts=apex.PartCollection()
	for new_part_name in NewPartNameList:
		#Check if part already exists
		new_part_full_name=new_assy_full_name + "/" + new_part_name
		try:
			new_part = apex.getPart( pathName = new_part_full_name )
			print("Part " + new_part_name + " already exists in assy " + new_assy_full_name)

		
		except:
			new_part = new_assy.createPart()
			updatedPartName_ = new_part.update( name = new_part_name )
			print("Created :" + new_part_name)
			
		parts.append(new_part)
	
	return new_assy,parts


def get_faces_near_bolt(bolt_solid,grow_box_rate=0.25,solid_names=[]):
	#
	## Faces ##
	
	faces=apex.entityCollection()
	# get all faces that are not belonging to bolt, nut or washer
	# These objects have user attribute "ParentPart"
	#solids=getAllSolids()
	#print("No of solids: " + str(len(solids)))
	for name in solid_names:
		solid=apex.getSolid( pathName = name )
		print("solid: " + solid.getName() )
		#tmp_attributes=solid.getUserAttributes(['ParentPart'])
		#if len(tmp_attributes) == 0:
		#	connector_solid=False
		#else:
		#	connector_solid=True
		#
		#if  connector_solid == False:
		faces += solid.getFaces()
		
	# for tmp_face in faces:
		# tmp_pnt=tmp_face.getCentroid()
		# tmpnt_3D=apex.construct.Point3D(tmp_pnt.getX(),tmp_pnt.getY(),tmp_pnt.getZ())
		# tmp_label="face_" + str(tmp_face.getId())
		# print(tmp_label)
		# tmp_txt=apex.display.displayText(tmp_label,tmpnt_3D)	
		
	
	print("No of solids: " + str(len(solid_names) ) )

	
	# get all solids in each zoom box
	zoombox=1
	if 1==1:
		z_solid_name=bolt_solid.getName()
		tmpBoxBodies = apex.entityCollection()
		
		z_box=bolt_solid.getAxisAlignedBoundingBox()
		
		
	IPhysCol=apex.IPhysicalCollection()	
	for face in faces:
		#solid.clearHighlight()
		IPhysCol.append(face.asEntity())
		
	proxy=apex.ProximitySearch()
	proxy.insertCollection(IPhysCol)
	
	
	P1, P2 = ScaleBoxPoints(z_box,grow_box_rate)
	proxy_faces=proxy.findObjectsAABoundingBox(P1, P2)
	#
	# Handle solids that are entirely inside the zoom_box
	#print("Len of proxy faces: " + str(len(proxy_faces)))
		
	return proxy_faces

def get_edges_near_bolt(bolt_solid,grow_box_rate=0.25):
	#
	## Faces ##
	
	edges=apex.entityCollection()
	# get all surfaces

	surfs=getAllSurfs()
	solids=getAllSolids()
	
	#print("No of surfs: " + str(len(surfs)))
	for surf in surfs:
		edges += surf.getEdges()
	
	for solid in solids:
		edges += solid.getEdges()
		
	# get all edges in each zoom box
	zoombox=1
	if 1==1:
		z_solid_name=bolt_solid.getName()
		tmpBoxBodies = apex.entityCollection()
		z_box=bolt_solid.getAxisAlignedBoundingBox()
			
	IPhysCol=apex.IPhysicalCollection()	
	for edge in edges:
		IPhysCol.append(edge.asEntity())
		
	proxy=apex.ProximitySearch()
	proxy.insertCollection(IPhysCol)
	
	P1, P2 = ScaleBoxPoints(z_box,grow_box_rate)
	proxy_edges=proxy.findObjectsAABoundingBox(P1, P2)
	#
	# Handle edges that are entirely inside the zoom_box
	#print("Len of proxy edges: " + str(len(proxy_edges)))
		
	return proxy_edges	

def get_bodies_near_bolt(bolt_solid,close_enough_tol):
	#

	bodies=apex.entityCollection()
	# get all meshed solids and surfaces
	surfs=getAllSurfs()
	solids=getAllSolids()
	
	for surf in surfs:
		if len(surf.getElements()) > 0:
			bodies.append(surf)
	
	for solid in solids:
		if len(solid.getElements()) > 0:
			bodies.append(solid)
	print("There are",len(bodies),"meshed bodies")
	if len(bodies) > 0:
		distance_ = apex.measureDistance( source = bolt_solid, target = bodies )
		indx=sorted(list(range(len(distance_))), key=lambda k: distance_[k],reverse=False  )
		
		close_bodies=apex.entityCollection()
		n=0
		while n < len(bodies):
			if distance_[indx[n]] <= close_enough_tol:
				close_bodies.append(bodies[indx[n]])
			else:
				break
			n+=1

		
		return close_bodies	

	else:
		return bodies
	
def get_solids_near_bolt(bolt_solid,grow_box_rate=0.25,exclude_solids_names=[]):
	#grow_box_rate=0.25
	## Solids ##
	#model_1 = apex.currentModel()
	#dummy_part = model_1.createPart()
	#solids=getAllSolids()
	# get all solids in each zoom box
	zoombox=1
	if 1==1:
		z_solid_name=bolt_solid.getName()
		tmpBoxBodies = apex.entityCollection()
		z_box=bolt_solid.getAxisAlignedBoundingBox()
		
	bolt_full_name_path= bolt_solid.getPath() + "/" + bolt_solid.getName()
	IPhysCol=apex.IPhysicalCollection()	
	for exclude_name in exclude_solids_names:
		solid=apex.getSolid( pathName = exclude_name )
		#solid.clearHighlight()
		# exclude actual bolt solid...Change 20190924... need to check full path...change 20200108
		
		if exclude_name != bolt_full_name_path  :
			IPhysCol.append(solid.asEntity())
		
	proxy=apex.ProximitySearch()
	proxy.insertCollection(IPhysCol)
	
	
	P1, P2 = ScaleBoxPoints(z_box,grow_box_rate)
	proxy_solids=proxy.findObjectsAABoundingBox(P1, P2)
	#proxy_solids=proxy.findObjectsAABoundingBox(z_box.getPoint1(), z_box.getPoint2())
	#
	# Handle solids that are entirely inside the zoom_box
	print("Len of proxy solids: " + str(len(proxy_solids)))
	#if len(proxy_solids) > 0 :
		#Add attribute but do not split
		#print("I am in here")
		
		#for proxy_solid in proxy_solids:
			#highlight_stuff(proxy_solid,255,255,0)
			#proxy_solid.addUserAttribute(userAttributeName = "box_status", stringValue = "inside" )
			
	return proxy_solids		


####
def isCylindrical(tmp_face,tol=0.01):
	# assume extra vertices are removed
	# 1 Get Surface Mid point
	# 2 Check so that there are exaclty 2 edges
	# 3 Check so that both edges have equal length
	
	# 3 If Pass, then check ratio between area and circumference to get height
	# Return True and height
		
	# compare relative length difference. diff must ne smaller than 1% of the length
	midPoint = tmp_face.getCentroid()

	# Get All vertices from tmp_face
	tmp_verts=tmp_face.getVertices()
	tmp_edges=tmp_face.getEdges()
	
	if len(tmp_edges) != 2:
		return False
	else:
		if abs(tmp_edges[0].getLength() - tmp_edges[1].getLength()  )/tmp_edges[0].getLength()   < tol:
			#height=tmp_face.getArea()/tmp_edges[0].getlength()
			return True
		else:
			return False


def getMeshedBodies():
	model_1 = apex.currentModel()
	
	meshed_objects = apex.entityCollection()

	solids = apex.entityCollection()
	surfs = apex.entityCollection()
	parts = model_1.getParts(True)

	for part in parts:
		solids += part.getSolids()
		surfs += part.getSurfaces()

	n=0
	m=0
	for solid in solids:

		if len(solid.getElements()) <= 0:
			solid.hide()		
		else:
			tmp_meshes=solid.getMeshes()
			for mesh in tmp_meshes:
				if mesh.getVisibility() == 1:
					solid.show()
					n+=1
				else:
					solid.hide()
			meshed_objects.append(solid)
			
	for surf in surfs:
		if len(surf.getElements()) <= 0:
			surf.hide()		
		else:
			tmp_meshes=surf.getMeshes()
			for mesh in tmp_meshes:
				if mesh.getVisibility() == 1:
					surf.show()
					m+=1
				else:
					surf.hide()
			meshed_objects.append(surf)
	print("There are : " + str(len(meshed_objects)) + " meshed objects in model: " + str(m) + " surfs and  " + str(n) + " solids")
	return meshed_objects

def CreateMaterial(material_name,E=210000.,poi=0.29,rho=7.85e-6):
	material_1 = apex.catalog.createMaterial( name = "Material 1", description = "", color = [ 64, 254, 250 ] )
	material_1.update(
		name = material_name,
	)
	material_1.update(
		elasticModulus = E,
	)
	material_1.update(
		poissonRatio = poi,
	)
	material_1.update(
		density = rho,
	)
	return material_1

def AssignMaterial(entities,mat):
	_target = apex.entityCollection()
	for item in entities:
		_target.append(item)
	apex.attribute.assignMaterial(material = mat, target = _target )

def CreateGlue(bodyL,tol=0.1):
	tmp_list= apex.entityCollection()
	for body in bodyL:
		tmp_list.append(body)
	glue_pairs = apex.attribute.createMeshIndependentTie(
		target = tmp_list,
		tiePairTolerance = tol,
		calculatePatchToleranceAutomatically = True
	)
	return glue_pairs

def get_NutDimension(name,type):
	#  name   In_dia out_dia nut_dia nut_height tot_height
	M= {}
	if type=='hex_flange':
		#Source https://www.engineersedge.com/hardware/metric_hex_flange_nuts__13730.htm
		# ANSI B18.2.4.4M
		M['m5']=[5,1.0,9.8,5.0,8]
		M['m6']=[6,1.1,12.2,6.0,10]
		M['m8']=[8,1.2,15.8,8.0,13]
		M['m10']=[10,1.5,19.6,10.0,15]
		M['m12']=[12,1.8,23.8,12.0,18]
		M['m14']=[14,2.1,27.6,14.0,21]
		M['m16']=[16,2.4,31.9,16.0,24]
		M['m20']=[20,3.0,39.9,20.0,30]
		
	if name not in M.keys():
		print("Dimension: " + name + " does not exist")
		return -1,-1,-1,-1,-1
	else:
	
	
		# nut_in_diam, head_flange_height, nut_out_diam, tot_height, nut_dia
		return M[name][0],M[name][1],M[name][2],M[name][3],M[name][4]

def get_WasherDimensions(type,name):
	M={}
	#  name   In_dia out_dia height
	#          0     1     2    
	M['m1']=[1.1,3,0.3]
	M['m1.2']=[1.3,3.5,0.3]
	M['m1.4']=[1.5,4,0.3]
	M['m1.6']=[1.7,4,0.3]
	M['m1.7']=[1.8,4.5,0.3]
	M['m2']=[2.2,5,0.3]
	M['m2.5']=[2.7,6,0.5]
	M['m2.6']=[2.8,7,0.5]
	M['m3']=[3.2,7,0.5]
	M['m3.5']=[3.7,8,0.5]
	M['m4']=[4.3,9,0.8]
	M['m5']=[5.3,10,1]
	M['m6']=[6.4,12,1.6]
	M['m7']=[7.4,14,1.6]
	M['m8']=[8.4,16,1.6]
	M['m10']=[10.5,20,2]
	M['m12']=[13,24,2.5]
	M['m14']=[15,28,2.5]
	M['m16']=[17,30,3]
	M['m18']=[19,34,3]
	M['m20']=[21,37,3]
	M['m22']=[23,39,3]
	M['m24']=[25,44,4]
	M['m27']=[28,50,4]
	M['m30']=[31,56,4]
	M['m33']=[34,60,5]
	M['m36']=[37,66,5]
	M['m39']=[40,72,6]
	M['m42']=[43,78,7]
	M['m45']=[46,85,7]
	M['m48']=[50,92,8]
	M['m52']=[54,98,8]
	M['m56']=[58,105,9]
	M['m60']=[62,110,9]
	M['m64']=[66,115,9]
	M['m68']=[70,120,10]
	M['m72']=[74,125,10]
	M['m76']=[78,135,10]
	M['m80']=[82,140,12]
	M['m85']=[87,145,12]
	M['m90']=[93,160,12]
	M['m100']=[104,175,14]
	if name not in M.keys():
		print("Dimension: " + name + " does not exist")
		return -1,-1,-1
	else:
		return M[name][0],M[name][1],M[name][2]

def get_BoltDimensions(type,name):
	#print("I am in 1")
	GoFlag=True
	if type=='HHCS' or type =='hex' :
		M= {}
		#  name   diam height head_dia max_dia
		#changing m4 head 
		#0     1     2       3
		M['m3']=[3,2.12,5.5,6.4]
		#M['m4']=[4,2.92,7,8.1]
		M['m4']=[4,2.65,7,8.1]
		M['m6']=[6,4.15,10,11.5]
		M['m8']=[8,5.65,13,15]
		M['m10']=[10,6.63,17,18.48]
		M['m12']=[12,7.76,19,20.78]
		M['m14']=[14,9.09,22,24.25]
		M['m16']=[16,10.32,24,27.71]
		M['m20']=[20,12.88,30,34.64]
		M['m24']=[24,15.44,36,41.57]
		M['m30']=[30,19.48,46,53.12]
		M['m36']=[36,23.38,55,63.51]
		M['m42']=[42,26.97,65,75.06]
		M['m48']=[48,31.07,75,86.6]
		M['m56']=[56,36.2,85,98.15]
		M['m64']=[64,41.32,95,109.7]
		M['m72']=[72,46.45,105,121.24]
		M['m80']=[80,51.58,115,132.72]
		M['m90']=[90,57.74,130,150.11]
		M['m100']=[90,63.9,145,167.43]				


	elif type=='hex_flange':
		M= {}
		#  name   diam head_flange_height head_flange_dia tot_head_height head_dia
	
		#0     1     2       3
		M['m5']=[5,1.0,9.8,5.0,8]
		M['m6']=[6,1.1,12.2,6.0,10]
		M['m8']=[8,1.2,15.8,8.0,13]
		M['m10']=[10,1.5,19.6,10.0,15]
		M['m12']=[12,1.8,23.8,12.0,18]
		M['m14']=[14,2.1,27.6,14.0,21]
		M['m16']=[16,2.4,31.9,16.0,24]
		M['m20']=[20,3.0,39.9,20.0,30]
		
		
		
		
		
	elif type=='SH' or type =='hex socket' or type =='allen':
		#pls note the output order
		M={}
		#NameDIAMETER THREAD_LENGTH  HEAD_Dia HEAD_HEIGHT
		M['m1.6']=[1.6,15,3,1.7]
		M['m2']=[2,16,3.8,2]
		M['m2.5']=[2.5,17,4.5,2.5]
		M['m3']=[3,18,5.5,3]
		M['m4']=[4,20,7,4]
		M['m5']=[5,22,8.5,5]
		M['m6']=[6,24,10,6]
		M['m8']=[8,28,13,8]
		M['m10']=[10,32,16,10]
		M['m12']=[12,36,18,12]
		M['m14']=[14,40,21,14]
		M['m16']=[16,44,24,16]
		M['m18']=[18,48,27,18]
		M['m20']=[20,52,30,20]
		M['m22']=[22,56,33,22]
		M['m24']=[24,60,36,24]
		M['m27']=[27,66,40,27]
		M['m30']=[30,72,45,30]
		M['m33']=[33,78,50,33]
		M['m36']=[36,84,54,36]
		M['m42']=[42,96,63,42]
		M['m48']=[48,108,72,48]
	
	elif type=='TX' or type=='torx':
		M= {}
		#  name   diam, height, head_dia, head_groove_dia
		#changing m4 head 
		#0     1     2       3
		M['m3']=[3,3,5.5,2.5]
		M['m4']=[4,4,7,3]
		M['m5']=[5,5,8.5,4]
		M['m6']=[6,6,10,5]
		M['m8']=[8,8,13,6]
		M['m10']=[10,10,16,8]
		M['m12']=[12,12,18,10]
		M['m16']=[16,16,24,14]
		M['m20']=[20,20,30,17]
		
	
	else:
		M={}
		print("Unsupported Type")
		GoFlag=False
		return M
	
	if name not in M.keys():
		print("Dimension: " + name + " does not exist")
		
		values=sorted([ int(item.replace('m','')) for item in M.keys() ])
		i=int(name.replace('m',''))

		nearest_under = min(values, key=lambda v: abs(v-i))
		nearest_over = min(values, key=lambda v: abs(v-i) and v<i)
		
		nearest_under_key='m' + str(nearest_under)
		nearest_over_key='m' + str(nearest_over)
		
		L1=M[nearest_under_key]
		L2=M[nearest_over_key]
		if nearest_over > nearest_under:
			print("average two nearest")
			M[name]=[ 0.5*(L1[n]+ L2[n]) for n in range(4) ]
		else:
		
			if i > values[-1]:
				print(str(i) + " larger than max key")
				M[name]=M['m' + str(values[-1])]
			else:
				print(str(i) + " lower than max key")
				M[name]=M['m' + str(values[0])]	
			
	return M[name]	
	#return M[name][0],M[name][1],M[name][2]	

def	transform_copy_surf(obj_to_copy,delta,dist,copy=True):
	entities_1 = apex.entityCollection()
	entities_1.append(obj_to_copy)
	
	tmp_part=obj_to_copy.getParent()
	surfs_in=tmp_part.getSurfaces()
	#print("No of surfs in: " + str(len(surfs_in) ) )
	#
	apex.transformTranslate( target = entities_1,
		direction = delta,
		distance = dist,
		makeCopy = True)

	# return the created entity
	surfs_out=tmp_part.getSurfaces()
	#print("No of surfs out: " + str(len(surfs_out) ) )
	
	surfs_in_ids=[item.getName() for item in surfs_in]
	
	#print(surfs_in_ids)

	for item in surfs_out:	
		if item.getName() not in surfs_in_ids:
			#print("Id is " +  item.getName() )
			out_item = item
		else:
			pass
			#print("Old id " + item.getName())
	return out_item

def	transform_copy_solids(obj_to_copy,delta,dist,copy=True):
	entities_1 = apex.entityCollection()
	entities_1.append(obj_to_copy)
	
	tmp_part=obj_to_copy.getParent()
	solids_in=tmp_part.getSolids()
	#print("No of solids in: " + str(len(solids_in) ) )
	#
	apex.transformTranslate( target = entities_1,
		direction = delta,
		distance = dist,
		makeCopy = True)

	# return the created entity
	solids_out=tmp_part.getSolids()
	#print("No of solids out: " + str(len(solids_out) )  + " in " + tmp_part.getName() )

	solids_in_ids=[item.getName() for item in solids_in]
	
	#print(solids_in_ids)
	
	for item in solids_out:	
		if item.getName() not in solids_in_ids:
			#print("Id is " +  item.getName() )
			out_item = item
		else:
			pass
			#print("Old id " + item.getName())
	return out_item

def doSketch_1(part_1,diam,surface_1,FirstPass=False):
	origin=[0.,0.]
	radius=diam/2
	faces = surface_1.getFaces()
	#print("Number of faces: " + str(len(faces)))
	_referenceFace = faces[0]
	sketch_1 = part_1.createSketchOnSurface(
		name = 'Sketch 1',
		face = _referenceFace,
		reverseNormal = False,
		alignSketchViewWithViewport = False
	)
	if FirstPass == True:
		sketchpoint_1 = sketch_1.createPoint(
			name = "Sketchpoint 1",
			location = Point2D( -0.000000000001, 0.000000000002 )
		)
	else:
		circle_1 = sketch_1.createCircleCenterPoint(
			name = "Circle 1",
			centerPoint = Point2D( origin[0], origin[1] ),
			pointOnCircle = Point2D( radius, -0. )
		)
	result = sketch_1.completeSketch( fillSketches = True )
	
	
	# Something is strange... the complete sketch  does sometimes not always return the created surface...
	# workaround... delete the input surface and return the number of surfs in the part

	
	return result

def doSketch_2(part_1,diam_outer,diam_inner,surface_1,FirstPass=False):
	origin=[0.,0.]
	radius_outer=diam_outer/2
	radius_inner=diam_inner/2
	faces = surface_1.getFaces()
	#print("Number of faces: " + str(len(faces)))
	_referenceFace = faces[0]
	sketch_1 = part_1.createSketchOnSurface(
		name = 'Sketch 1',
		face = _referenceFace,
		reverseNormal = False,
		alignSketchViewWithViewport = False
	)
	if FirstPass == True:
		sketchpoint_1 = sketch_1.createPoint(
			name = "Sketchpoint 1",
			location = Point2D( -0.000000000001, 0.000000000002 )
		)
	else:
		circle_1 = sketch_1.createCircleCenterPoint(
			name = "Circle 1",
			centerPoint = Point2D( origin[0], origin[1] ),
			pointOnCircle = Point2D( radius_outer, -0. )
		)
		circle_2 = sketch_1.createCircleCenterPoint(
			name = "Circle 2",
			centerPoint = Point2D( origin[0], origin[1] ),
			pointOnCircle = Point2D( radius_inner, -0. )		
		)
	result = sketch_1.completeSketch( fillSketches = True )
	return result

def highlight_stuff(item,r,g,b):
	col1=apex.ColorRGB(r,g,b)					
	#if surf.getVisibility() == 1:		
	item.highlight(col1,2,3)
	
def print_coords(p):
	print("x: " + "%8.3f" % p[0] + "   y: " + "%8.3f" % p[1]  +   "   z: " + "%8.3f" % p[2])

def cross(u, v):
	u1 = u[0]
	u2 = u[1]
	u3 = u[2]
	v1 = v[0]
	v2 = v[1]
	v3 = v[2]
	vect = [u2 * v3 - u3 * v2, u3 * v1 - u1 * v3, u1 * v2 - u2 * v1]
	return vect	

def getLength(cord1, cord2=[0.,0.,0.]):
	dist = ((cord2[0] - cord1[0]) ** 2 + (cord2[1] - cord1[1]) ** 2 + (cord2[2] - cord1[2]) ** 2) ** 0.5
	return dist	
	
def getDelta(cord1, cord2):
	delta=[cord2[0]-cord1[0], cord2[1]-cord1[1], cord2[2]-cord1[2]]
	return delta
	
def addVectors(cord1, cord2):
	vec_sum=[cord2[0]+cord1[0], cord2[1]+cord1[1], cord2[2]+cord1[2]]
	return vec_sum	

def scaleVector(cord1,scale_fact):
	scaled_vect=[ scale_fact*cord1[0],scale_fact*cord1[1],scale_fact*cord1[2] ]
	return scaled_vect

def addVectors(cord1, cord2):
	vect_sum=[cord2[0]+cord1[0], cord2[1]+cord1[1], cord2[2]+cord1[2]]
	return vect_sum	

def getThreePointsInPlane(pA,pB):
	lgth=getLength(pA,pB)

	#normalize axis	
	v=[i/lgth for i in [pB[0]-pA[0], pB[1]-pA[1], pB[2]-pA[2]] ]
	#print(v)
	#get d:
	d=v[0]*pA[0]+v[1]*pA[1]+v[2]*pA[2]
	
	tol=1e-2

	# find two additional points that will lie in the plane
	# generate a random vector, and project it on to the plane
	
	# try new vB's until it is not colinear to vA
	while True:
		vA=[10*random.random(),10*random.random(),10*random.random()]
		vB=[10*random.random(),10*random.random(),10*random.random()]
		if len(cross(vA,vB)) > tol and len(cross(v,vA)) > tol and len(cross(v,vB)) > tol:
			break
			#

	proj_vA=cross(v,vA)
	proj_vB=cross(v,vB)

	#new approach, only need coordinates
	if 1==1:
		p01=[pA]
		p02=[pA[0] + proj_vA[0],pA[1] + proj_vA[1],pA[2] + proj_vA[2] ]
		p03=[pA[0] + proj_vB[0],pA[1] + proj_vB[1],pA[2] + proj_vB[2] ]
		
		return [p01,p02,p03]

	# old_approach- 
	if 1==0:
		# need to add vectors to its original points		
		# create a filler of 3 points on the plane
		point_1 = apex.geometry.createPointXYZ( x = pA[0], y = pA[1], z = pA[2] ).asPoint()
		point_2 = apex.geometry.createPointXYZ( x = pA[0] + proj_vA[0], y = pA[1] + proj_vA[1], z = pA[2] + proj_vA[2] ).asPoint()	
		point_3 = apex.geometry.createPointXYZ(  x = pA[0] + proj_vB[0], y = pA[1] + proj_vB[1], z = pA[2] + proj_vB[2] ).asPoint()		
		_target = apex.entityCollection()		
		_target += point_1.getVertices()
		_target += point_2.getVertices()
		_target += point_3.getVertices()
		
		return _target
		
def old_CreateCircleAtPt(pA,pB,diam,bolt_part,model_1):

	#Create tmp_part if not existing
	try:
		tmp_part = model_1.getPart( pathName = "tmp_Filler" )
	except:
		tmp_part = model_1.createPart()
		updatedPartName_ = tmp_part.update( name = "tmp_Filler" )

	#tmp_part = model_1.createPart()
	#updatedPartName_ = tmp_part.update( name = "tmp_Filler" )


	lgth=getLength(pA,pB)

	#normalize axis	
	v=[i/lgth for i in [pB[0]-pA[0], pB[1]-pA[1], pB[2]-pA[2]] ]
	#print(v)
	#get d:
	d=v[0]*pA[0]+v[1]*pA[1]+v[2]*pA[2]
	
	tol=1e-2

	SuccessFlag=False
	LoopFlag=0
	while SuccessFlag==False:

		if 1==1:
			# find two additional points that will lie in the plane
			# generate a random vector, and project it on to the plane
			
			# try new vB's until it is not colinear to vA
			while True:
				vA=[10*random.random(),10*random.random(),10*random.random()]
				vB=[10*random.random(),10*random.random(),10*random.random()]
				if len(cross(vA,vB)) > tol and len(cross(v,vA)) > tol and len(cross(v,vB)) > tol:
					break
			#print vB
			if 1==0:
				print_coords(v)
				print_coords(vA)
				print_coords(vB)
				print("________")
				#
		# New improved code
		# vA and vB are not colinear
		# need to make sure they are not colinear with v
		
		#vA=[10,0,0]
		#vB=[0,10,0]	
		
		#if len(cross(v,vA)) <  tol:
		#	vA=[10,10,0]
		#if len(cross(v,vB)) <  tol:
		#	vB==[0,10,10]
			
		proj_vA=cross(v,vA)
		proj_vB=cross(v,vB)

		# need to add vectors to its original points
		
		# create a filler of 3 points on the plane
		point_1 = apex.geometry.createPointXYZ( x = pA[0], y = pA[1], z = pA[2] ).asPoint()
		point_2 = apex.geometry.createPointXYZ( x = pA[0] + proj_vA[0], y = pA[1] + proj_vA[1], z = pA[2] + proj_vA[2] ).asPoint()	
		point_3 = apex.geometry.createPointXYZ(  x = pA[0] + proj_vB[0], y = pA[1] + proj_vB[1], z = pA[2] + proj_vB[2] ).asPoint()
		
		_target = apex.entityCollection()
		
		_target += point_1.getVertices()
		_target += point_2.getVertices()
		_target += point_3.getVertices()
		filler_surf = apex.geometry.fillerSurface( target = _target ).asSurface()
		
		surfs=doSketch_1(tmp_part,diam,filler_surf,False)
		
		#delete filler surf and points
		entities_1 = apex.entityCollection()
		entities_1.append(filler_surf)
		entities_1.append(point_1)
		entities_1.append(point_2)
		entities_1.append(point_3)
		apex.deleteEntities(entities_1)
		
		surfs=tmp_part.getSurfaces()
		
		
		try:
			#print("Number of entities from sketch: " + str(len(surfs)))
			slask=len(surfs)
			SuccessFlag=True
		except:
			LoopFlag+=1
			print("Number of entities from sketch: " + str(len(surfs)))
			print("Loop: " + str(LoopFlag) + "   Something went wrong in Sketch creating the circle:")
			print("Try again")
			SuccessFlag=False	
		
		# stop after 100 attempts
		if LoopFlag == 10:
			print("Max no of iterrations " + str(LoopFlag) + "reached")
			SuccessFlag=True
	
	# move Surface to the correct location
	
	surf_origin=surfs[0].getLocation()
	delta_vect=[pA[0] - surf_origin.getX(), pA[1] -surf_origin.getY() , pA[2] -surf_origin.getZ() ]
	
	apex.transformTranslate( target = surfs,
		direction = delta_vect,
		distance = getLength(delta_vect),
		makeCopy = False)		

	try:
		apex.reparent( parent = bolt_part, target = surfs )
	except:
		pass

	#tmp_part.delete()
	return surfs[0]

def CreateRingAtPt(pA,pB,diam_outer,diam_inner,bolt_part,model_1):
	
	#Create tmp_part if not existing
	try:
		tmp_part = model_1.getPart( pathName = "tmp_Filler" )
	except:
		tmp_part = model_1.createPart()
		updatedPartName_ = tmp_part.update( name = "tmp_Filler" )
	
	#tmp_part = model_1.createPart()
	#updatedPartName_ = tmp_part.update( name = "tmp_Filler" )

	lgth=getLength(pA,pB)

	#normalize axis	
	v=[i/lgth for i in [pB[0]-pA[0], pB[1]-pA[1], pB[2]-pA[2]] ]
	#print(v)
	#get d:
	d=v[0]*pA[0]+v[1]*pA[1]+v[2]*pA[2]
	
	tol=1e-2
	
	SuccessFlag=False
	LoopFlag=0
	while SuccessFlag==False:
		# find two additional points that will lie in the plane
		# generate a random vector, and project it on to the plane

		# try new vB's until it is not colinear to vA
		while True:
			vA=[10*random.random(),10*random.random(),10*random.random()]
			vB=[10*random.random(),10*random.random(),10*random.random()]
			if len(cross(vA,vB)) > tol and len(cross(v,vA)) > tol and len(cross(v,vB)) > tol:
				break

		proj_vA=cross(v,vA)
		proj_vB=cross(v,vB)

		# need to add vectors to its original points
		# create a filler of 3 points on the plane
		point_1 = apex.geometry.createPointXYZ( x = pA[0], y = pA[1], z = pA[2] ).asPoint()
		point_2 = apex.geometry.createPointXYZ( x = pA[0] + proj_vA[0], y = pA[1] + proj_vA[1], z = pA[2] + proj_vA[2] ).asPoint()	
		point_3 = apex.geometry.createPointXYZ(  x = pA[0] + proj_vB[0], y = pA[1] + proj_vB[1], z = pA[2] + proj_vB[2] ).asPoint()		
		
		_target = apex.entityCollection()
		
		_target += point_1.getVertices()
		_target += point_2.getVertices()
		_target += point_3.getVertices()
		filler_surf = apex.geometry.fillerSurface( target = _target ).asSurface()
		
		#
		surfs=doSketch_2(tmp_part,diam_outer,diam_inner,filler_surf,False)
		
		#delete filler surf and points, hence no need to delete the part
		entities_1 = apex.entityCollection()
		entities_1.append(filler_surf)
		entities_1.append(point_1)
		entities_1.append(point_2)
		entities_1.append(point_3)
		
		try:
			print("ok")
			no_of_surfs=len(surfs)
		except:
			print("nok")
			surfs=tmp_part.getSurfaces()
		
			
		try:
			#print("Number of entities from sketch: " + str(len(surfs)))
			slask=len(surfs)
			print("Number of surfs (slask)" + str(slask))
			SuccessFlag=True

		except:
			LoopFlag+=1
			print("Loop: " + str(LoopFlag) + "   Something went wrong in Sketch creating the ring:")
			print("Try again")
			SuccessFlag=False
			#delete created geom, start over
			apex.deleteEntities(entities_1)
			
		# stop after 100 attempts
		if LoopFlag == 10:
			print("Max no of iterrations " + str(LoopFlag) + "reached")
			SuccessFlag=True
		

	# delete the face with just one edge
	
	tmp_faces=surfs[0].getFaces()
	
	for tmp_face in tmp_faces:
		tmp_edges=tmp_face.getEdges()
		if len(tmp_edges) > 1:
			#This is outer face, keep
			pass
		else:
			#Inner circel, delete
			entities_1.append(tmp_face)
			
	apex.deleteEntities(entities_1)		
	
	print("No of surfaces after created ring: " + str(len(surfs)))
	
	# move Surface to the correct location	
	surf_origin=surfs[0].getLocation()
	print("Success moved surf")
	delta_vect=[pA[0] - surf_origin.getX(), pA[1] -surf_origin.getY() , pA[2] -surf_origin.getZ() ]
	
	apex.transformTranslate( target = surfs,
		direction = delta_vect,
		distance = getLength(delta_vect),
		makeCopy = False)		
	try:
		apex.reparent( parent = bolt_part, target = surfs )
	except:
		print("Could not reparent to bolt_part")
		pass

	# delete tmp part	
	#tmp_part.delete()
	
	return surfs[0]	
		
def slice_solid_split(solid,surf):
	_target = apex.entityCollection()
	_target.append( solid )
	_face = apex.entityCollection()
	_face += surf.getFaces()
	solids = apex.geometry.splitOnSurface(
		target = _target,
		face = _face,
		splitBehavior = apex.geometry.GeometrySplitBehavior.Split
		)
	return solids

def slice_solid_partition(solid,surf):
	print("Split partition")
	_target = apex.entityCollection()
	_target.append( solid )
	_face = apex.entityCollection()
	_face += surf.getFaces()
	result = apex.geometry.splitOnSurface(
		target = _target,
		face = _face,
		splitBehavior = apex.geometry.GeometrySplitBehavior.Partition
		)
	
	out_cells=apex.entityCollection()
	for solid in result:
		out_cells.extend(solid.getCells())
	
	return out_cells
	
def slice_solid_partition2(cell,surf):
	print("Split partition")
	_target = apex.entityCollection()
	_target.append( cell )
	_face = apex.entityCollection()
	_face += surf.getFaces()
	result = apex.geometry.splitOnSurface(
		target = _target,
		face = _face,
		splitBehavior = apex.geometry.GeometrySplitBehavior.Partition
		)
	
	#out_cells=apex.entityCollection()
	#for solid in result:
	#	out_cells.extend(solid.getCells())
	
	return result	
	
		
def CreateCircleAtPt(p1,p2,diam,bolt_part,model_1):
	# return surface
	tmp_pnts=getThreePointsInPlane(p1,p2)
	v_extrude=getDelta(tmp_pnts[1],p1)

	#p10 is the point at the extension of p1 p2 on the radius
	radius_vect=scaleVector(v_extrude,0.5*diam/getLength(v_extrude) )
	
	p10= addVectors(p1,radius_vect)
	# create curve
	
	_target = [apex.Coordinate( p1[0], p1[1], p1[2] ), apex.Coordinate( p10[0], p10[1], p10[2]) ]
	_iphysicalCollection = apex.IPhysicalCollection()	
	for point in _target :
		_iphysicalCollection.append(point)

	_axisVector = apex.construct.Vector3D( p2[0]-p1[0], p2[1]-p1[1], p2[2]-p1[2] )
	_axisPoint = Point3D( p1[0], p1[1], p1[2])
		
	tmp_radius = apex.geometry.createCurve(target = _iphysicalCollection, behavior = apex.geometry.CurveBehavior.Spline)
	
	_target_source = apex.EntityCollection()
	_target_source.append(tmp_radius)
	
	result = apex.geometry.createGeometryRevolve(
		target = _target_source,
		axisVector = _axisVector,
		axisPoint = _axisPoint,
		angleOfRevolution = 360.,
		startOffsetAngle = 0.0
	)
	tmp_surfs=model_1.getCurrentPart().getSurfaces()	
	no_of_surfs=len(tmp_surfs)	
	print("Created Surface Name: " + tmp_surfs[no_of_surfs-1].getName() )
	
	
	# delete radius vect
	if len(_target_source) != 0:
		apex.deleteEntities(_target_source)
	
	return tmp_surfs[no_of_surfs-1] 
		
def CreateCylinder(p1,p2,diam,bolt_part,model_1):
	# Set Current Part
	model_name=model_1.getName()
	N=len(model_name) + 1
	parent_part_name=bolt_part.getPathName()[N:]
	print("Current Part name: " + parent_part_name )
	try:
		model_1.setCurrentPart(parent_part_name )
	except:
		pass
		print("Failed")
		curr_part=model_1.getCurrentPart()
		print("The actual current part is: " + curr_part.getName() )
	
	
	
	# create cylinder axis vect
	_CylAxisVect = apex.construct.Vector3D( p2[0]-p1[0], p2[1]-p1[1], p2[2]-p1[2] )
	
	#  create surface to be swept
	surf1=CreateCircleAtPt(p1,p2,diam,bolt_part,model_1)

	_source = apex.EntityCollection()
	_source.append( surf1 )

	#part_1 = model_1.setCurrentPart(pathName = bolt_part.getName() )
	_result= apex.geometry.createGeometryExtrude(target = _source, extrudeVector = _CylAxisVect, extrusionDistance = getLength(p2,p1), startOffsetDistance = 0.0)
	
	tmp_solids=model_1.getCurrentPart().getSolids()
	no_of_solids=len(tmp_solids)	
	
	# Creted solid:
	new_solid=tmp_solids[no_of_solids-1]
	print("Created Solid: " + new_solid.getName() )
	
	entities_1 = apex.EntityCollection()
	#entities_1.append(tmp_solids[no_of_solids-1])
	entities_1.append(new_solid)
	
	
	
	#try:
	#	apex.reparent( parent = bolt_part, target = entities_1 )
	#except:
	#	pass
		
	
	return new_solid, surf1

def CreateCone(p1,p2,diam1,diam2,bolt_part,model_1):
	# Set Current Part
	model_name=model_1.getName()
	N=len(model_name) + 1
	parent_part_name=bolt_part.getPathName()[N:]
	print("Current Part name: " + parent_part_name )
	
	print("diam1",diam1)
	print("diam2",diam2)
	
	try:
		model_1.setCurrentPart(parent_part_name )
	except:
		pass
		print("Failed")
		curr_part=model_1.getCurrentPart()
		print("The actual current part is: " + curr_part.getName() )
	
	
	axis_delta=[p2[0]-p1[0], p2[1]-p1[1], p2[2]-p1[2]]
	# create cylinder axis vect
	_CylAxisVect = apex.construct.Vector3D( p2[0]-p1[0], p2[1]-p1[1], p2[2]-p1[2] )
	
	#  create surface to be swept
	surf1=CreateCircleAtPt(p1,p2,diam1,bolt_part,model_1)
	

	
	# get point on surf1 edge
	edge=surf1.getEdges()[0]
	pnt=edge.getMidPoint()
	
	pntA=[pnt.getX(),pnt.getY(),pnt.getZ()]
	
	# create loaction for lower outer diameter
	pAp1=[pntA[0]-p1[0],pntA[1]-p1[1],pntA[2]-p1[2]]
	# create loaction for upper outer diameter
	pntB=[p2[0] +diam2/diam1*pAp1[0], p2[1] +diam2/diam1*pAp1[1], p2[2] +diam2/diam1*pAp1[2] ]

	#CreateCurves
	curve1=createCurveFromCords(p1,p2)
	curve2=createCurveFromCords(pntA,pntB)
	
	# create fillet to rotate
	_target = apex.EntityCollection()
	_target.append(curve1)
	_target.append(curve2)
	surf2 = apex.geometry.fillerSurface( target = _target )
	
	_target = apex.EntityCollection()
	_target.append( surf2 )

	_axisPoint = Point3D( p1[0], p1[1], p1[2])
	new_solid = apex.geometry.createGeometryRevolve(
		target = _target,
		axisVector = _CylAxisVect,
		axisPoint = _axisPoint,
		angleOfRevolution = 3.600000000000000e+02,
		startOffsetAngle = 0.0
	)
	
	tmp_solids=model_1.getCurrentPart().getSolids()
	no_of_solids=len(tmp_solids)
	# Creted solid:
	new_solid=tmp_solids[no_of_solids-1]
	print("Created Solid: " + new_solid.getName() )	
	entities_1 = apex.EntityCollection()	#entities_1.append(tmp_solids[no_of_solids-1])
	entities_1.append(new_solid)
	
	
	
	
	entities_1 = apex.EntityCollection()
	entities_1.append(curve1)
	entities_1.append(curve2)
	#entities_1.append(surf1)
	entities_1.append(surf2)
	apex.deleteEntities(entities_1)
		
	return new_solid, surf1

def CreateTXHead(p1,p2,head_diam,slot_dia,bolt_part,model_1):

	# create head
	head0, head_surf =CreateCylinder(p1,p2,head_diam,bolt_part,model_1)
	print("solid 1 created: " + head0.getName() )
	# create subtracting cylinder
	
	# create subtracting cylinder
	vect=getDelta(p1,p2)  # groove is 30% of head height
	head_scale_fact=0.7
	scale_vect=[item*head_scale_fact for item in vect]
	
	# new p3
	p3=[p2[0] - scale_vect[0], p2[1] - scale_vect[1], p2[2] - scale_vect[2] ]
	slot,new_surf_2=CreateCylinder(p2,p3,slot_dia,bolt_part,model_1)
	print("solid 2 created:  " + slot.getName() )
	
	

	
	
	


	_target=apex.EntityCollection()
	_target.append( head0 )
	_subtractingEntities = apex.EntityCollection()
	_subtractingEntities.append( slot )
	
	result = apex.geometry.subtractBoolean(
		target = _target,
		subtractingEntity = _subtractingEntities,
		retainOriginalBodies = False
	)
	print("Subtracted")
	head=result[0]
	
	# slice head with inner groove face to avoid internal split loops
	#get the cylindrical surfaces of head 


	target = apex.entityCollection()
	_target.append( head )
	_face = apex.entityCollection()
	
	
	solid_faces = head.getFaces()
	
	for solid_face in solid_faces:
		if isCylindrical(solid_face):
			_face.append(solid_face)
		
	
	try:
		result = apex.geometry.splitOnSurface(
			target = _target,
			face = _face,
			splitBehavior = apex.geometry.GeometrySplitBehavior.Partition
		)
	
	except:
		print("fail slice 2")
	
	
	
	
	

	# slice head with head surf translated to 0.3 of head
	mid_surf=transform_copy_surf(head_surf,getDelta(p1,p2),0.3*getLength(p1,p2),copy=True)
	
	try:
		#if 1==1:
		slice_solid_partition(head, mid_surf)
		_tmp.append(mid_surf)			
	except:
		print("fail slice 1")
	
	
	

	
	

	# Set Current Part
	model_name=model_1.getName()
	N=len(model_name) + 1
	parent_part_name=bolt_part.getPathName()[N:]
	print("Current Part name: " + parent_part_name )
	try:
		model_1.setCurrentPart(parent_part_name )
	except:
		pass
		print("Failed")
		curr_part=model_1.getCurrentPart()
		print("The actual current part is: " + curr_part.getName() )
		
	
	return head, head_surf
	
def CreateSolidRing(p1,p2,diam_outer,diam_inner,bolt_part,model_1):
	# part_name= bolt_part.getPathName()
	# print("Bolt part: " + part_name )
	# part_1 = model_1.setCurrentPart(pathName = bolt_part.getPathName() )
	#part_1 = model_1.setCurrentPart(pathName = "MountAssembly/Bolts/Washers")
	#surf1=CreateRingAtPt(p1,p2,diam_outer,diam_inner,bolt_part,model_1)
	outer_solid,outer_surf=CreateCylinder(p1,p2,diam_outer,bolt_part,model_1)	
	
	inner_solid,inner_surf=CreateCylinder(p1,p2,diam_inner,bolt_part,model_1)
	
	_target = apex.EntityCollection()
	
	#print("**Outer** " + str(len(outer_solid)))
	#print("**Inner** " + str(len(inner_solid)))
	
	_target.append( outer_solid )
	_subtractingEntities = apex.EntityCollection()
	_subtractingEntities.append( inner_solid )
	
	result = apex.geometry.subtractBoolean(
		target = _target,
		subtractingEntity = _subtractingEntities,
		retainOriginalBodies = False
	)
	#entities_1 = apex.EntityCollection()
	#entities_1.append(result[0])
	
	# delete surfaces
	_target = apex.EntityCollection()
	_target.append(inner_surf)
	_target.append(outer_surf)
	apex.deleteEntities(_target)
	
	#apex.reparent( parent = bolt_part, target = entities_1 )
	
	return result[0]
	
def old_CreateSolidRing(p1,p2,diam_outer,diam_inner,bolt_part,model_1):
	surf1=CreateRingAtPt(p1,p2,diam_outer,diam_inner,bolt_part,model_1)
	
	# copy surf to use for slicing
	#surf1_cpy=transform_copy_surf(surf1,[1.,0.,0.],0.,copy=True)
	
	
	#entities_1 = apex.entityCollection()
	#entities_1.append(surf1)		
	#surf2=transform_copy_surf(surf1,getDelta(p1,p2),getLength(p1,p2),copy=True)
	#tmp_face1=surf1.getFaces()[0]
	#tmp_face1_id=tmp_face1.getId()

	_target = apex.entityCollection()
	_target += surf1.getFaces()
	result = apex.geometry.pushPull(
		target = _target,
		method = apex.geometry.PushPullMethod.Normal,
		behavior = apex.geometry.PushPullBehavior.Extrude,
		removeInnerLoops = False,
		createBooleanUnion = False,
		distance = getLength(p1,p2),
		direction = getDelta(p1,p2)
	)
	
	return result[0]	


def create_HexFlangeNut(p1,p2,head_flange_dia,head_flange_height,head_height,head_diam,bolt_diam,nut_part,model_1):
	_tmp=apex.entityCollection()
	vect=getDelta(p1,p2)
	# flange
	head_scale_fact=head_flange_height/getLength(p1,p2)
	print("Nut head_flange_height",head_flange_height)
	scale_vect=[item*head_scale_fact for item in vect]
	#
	#p3=[p1[0] - scale_vect[0], p1[1] - scale_vect[1], p1[2] - scale_vect[2] ]
	p3=[p1[0] + scale_vect[0], p1[1] + scale_vect[1], p1[2] + scale_vect[2] ]
	head,head_surf=CreateCylinder(p1,p3,head_flange_dia,nut_part,model_1)
	print("Head surf created: " + head_surf.getName() )

	# head
	head_scale_fact=head_height/getLength(p1,p2)
	scale_vect=[item*head_scale_fact for item in vect]
	#
	
	# get new p1... (the previous P3)
	p1new=p3
	
	#p3new=[p1[0] - scale_vect[0], p1[1] - scale_vect[1], p1[2] - scale_vect[2] ]
	#p3new=[p1new[0] - scale_vect[0], p1new[1] - scale_vect[1], p1new[2] - scale_vect[2] ]
	p3new=[p1new[0] + scale_vect[0], p1new[1] + scale_vect[1], p1new[2] + scale_vect[2] ]
	print("head_diam",head_diam)
	head_top,head_top_surf=CreateCylinder(p1new,p3new,head_diam,nut_part,model_1)
	print("Head Top surf created: " + head_top_surf.getName() )
	_tmp.append(head_top_surf)
	# combine head flange and head top to one solid
	
	_target = apex.EntityCollection()
	_target.append( head_top )
	_target.append( head )
	result = apex.geometry.mergeBoolean(
		target = _target,
		retainOriginalBodies = False,
		mergeSolidsAsCells = False
	)
	nut_solid=result[0]
	#nut_solid=result
	print("ready to create subtract cylinder")
	
	# subtract diameter from head to get hole
	p1_=[p1[0] - scale_vect[0], p1[1] - scale_vect[1], p1[2] - scale_vect[2] ]
	p2_=[p2[0] + scale_vect[0], p2[1] + scale_vect[1], p2[2] + scale_vect[2] ]
	
	hole,hole_surf=CreateCylinder(p1_,p2_,bolt_diam,nut_part,model_1)
	_target=apex.EntityCollection()
	_target.append( nut_solid )
	_subtractingEntities = apex.EntityCollection()
	_subtractingEntities.append( hole )
	
	result = apex.geometry.subtractBoolean(
		target = _target,
		subtractingEntity = _subtractingEntities,
		retainOriginalBodies = False
	)
	print("Subtracted")
	head=result[0]
	#head=result
	
	
	# split head with head-top cylindrical face to avoid floating splits
	# split face is the cylinder with area: head_diam*pi*dist(p1new,p3new)
	height=getLength(p1new,p3new)
	A0=head_diam*3.141*height
	
	for tmp_face in head.getFaces():
		tmpA=tmp_face.getArea()
		if abs(tmp_face.getArea() - A0 ) < 1.0 :
			split_face=tmp_face
			break
			
	#slice_solid_partition(head, split_face)
	
	_target = apex.entityCollection()
	_target.append( head )
	_face = apex.entityCollection()
	_face.append( split_face )
	result = apex.geometry.splitOnSurface(
		target = _target,
		face = _face,
		splitBehavior = apex.geometry.GeometrySplitBehavior.Partition
	)
	
	print("Split top face")
	
	#delete tmp surfs
	apex.deleteEntities(_tmp)
	print("Deleted Temp")
	return head
	
def CreateBolt2(BoltDict, bolt_part=None, washer_part=None, nut_part=None, mesh_size=None, bolt_mat_name="Bolt_Mat", MentatBolts=False,splitLocation="",splitPercentage=0,splitDistance=0,boltTipGlueTolerance=0.5,boltTopTouchTolerance=0.1,boltFriction=0.15,preLoad="Table",writeToCurvePart_Flag=False):

	#MentatBolts=False
	#MentatBolts=True
	
	model_1 = apex.currentModel()
	# count number of copies created
	orig_cnt=0
	instance_cnt=0
	
	#master entities will be stored in this dict and used to spawn copies
	#for multiple entities with same dimension 
	#limitation: only allow for entities with an identical orientation
	EntityMasterD={}

	_GlueL = []
	_TouchL = []
	
	_GlueL2 = []
	_TouchL2 = []
	
	BoltHeads=apex.entityCollection()
	BoltTips=apex.entityCollection()
	HeadWashers=apex.entityCollection()
	NutWashers=apex.entityCollection()
	Nuts=apex.entityCollection()
	
	#print("No of items in BoltDict: " + str(len(BoltDict.keys())))
	#print(BoltDict)
	#print("==============")
	
	# get all solids and all surfs
	# add items with mesh to glue-candidate list
	
	TouchCandidates=getMeshedBodies()
	
	for item in BoltDict.keys():
	
		if 1==0:
			#if bolt_part == None:
			#print("Creating BoltPart")
			bolt_part = bolts_assy.createPart()
			updatedPartName_ = bolt_part.update( name = BoltName )
			#assign material			
			bolt_mat = apex.catalog.getMaterial( name = bolt_mat_name )
			_target = apex.entityCollection()
			_target.append(bolt_part)
			apex.attribute.assignMaterial(material = bolt_mat, target = _target )
	
		# This list contains items that will be checked to create touching contacts to other parts
		TouchObjects=apex.entityCollection()
		# This list contains items that will be checked to create glued contacts to other parts
		# typically bolt tips where no nut is found
		GlueObjects=apex.entityCollection()
	
	
		bolt_data=BoltDict[item]
		#print(bolt_data)
		p1=bolt_data[0]
		p2=bolt_data[1]
		name=bolt_data[2]   # this is m8 or m12
		type=bolt_data[3]
		Nut=bolt_data[4]
		WasherHead=bolt_data[5]
		WasherNut=bolt_data[6]
		head_diam=bolt_data[7]
		head_height=bolt_data[8]

		vect=getDelta(p1,p2)
		# print("vect")
		# print(vect)
		# print("p1")
		# print(p1)
		# print("p2")
		# print(p2)

		#get bolt dimensions
		#print("type " + type)
		#print("name " + name)

		M_temp=get_BoltDimensions(type,name)
		
		

			
		GoFlag=True
		if type=='HHCS' or type =='hex' or type=='TX' or type=='torx':
			diam=M_temp[0]
			head_height=M_temp[1]
			head_diam=M_temp[2]
			# change stefan 200405 bug in TX detected -> temp fix
			type='hex'
		elif type=='SH' or type =='hex socket' or type =='allen':
			diam=M_temp[0]
			#head_height=M_temp[1]
			head_height=M_temp[3]
			head_diam=M_temp[2]
		elif type=='hex_flange':
			#diam head_flange_height head_flange_dia tot_head_height head_dia
			diam=M_temp[0]
			head_flange_height=M_temp[1]
			head_flange_dia=M_temp[2]
			head_height=M_temp[3]-head_flange_height
			head_diam=M_temp[4]
		# change stefan 200405 bug in TX detected -> temp fix	
		#elif type=='TX' or type=='torx':
		#	diam=M_temp[0]
		#	head_height=M_temp[1]
		#	head_diam=M_temp[2]
		#	slot_dia=M_temp[3]
		#
		elif type=='pin':
			diam=float(name.replace('m',''))
			head_height=0
			head_diam=0
			print("Found pin with diameter: ",diam)
		
		elif type=='hex_flange':
			#diam head_flange_height head_flange_dia tot_head_height head_dia
			diam=M_temp[0]
			head_flange_height=M_temp[1]
			head_flange_dia=M_temp[2]
			head_height=M_temp[3]-head_flange_height
			head_diam=M_temp[4]
		
		elif type=='rivet' or type=='countersunk':
			#diam head_flange_height head_flange_dia tot_head_height head_dia
			diam= float(name.replace('m',''))
			head_height=float(head_height)
			head_diam=float(head_diam)

		else:
			print("***Failed to find type***")
			print("type " + type)
			print("name " + name)
			GoFlag=False
	
		if GoFlag:
		
			#print("###")
			#print("Head Diameter: " + str(head_diam) + " head height: " + str(head_height) )
			
			#
			#print(name + " " +  type + " " + str(Nut) + " " + str(WasherHead) + " " + str(WasherNut) )
			
			BoltName="Bolts_" + BoltDict[item][2] + "_" + BoltDict[item][3]
			
			# add preload in the name
			if preLoad != "Table":
				BoltName += "#" + str(preLoad)
			
			#print(BoltName)
			
			common_parent, AssyFlag=getCommonParent(p1,p2,diam)
			if AssyFlag:
				new_assy,connect_parts=CreateAssyAndPartsinAssy(common_parent, "3DConnections" ,NewPartNameList=[BoltName,"Nuts","Washers"])
				bolt_part=connect_parts[0]
				nut_part=connect_parts[1]
				washer_part=connect_parts[2]
			else:
				bolt_part=common_parent
				nut_part=common_parent
				washer_part=common_parent
				
			# check if Bolt part already exists
			#bolt_part=bolts_assy.getPart( BoltName )
			
			#assign material			
			bolt_mat = apex.catalog.getMaterial( name = bolt_mat_name )
			_target = apex.entityCollection()
			_target.append(bolt_part)
			_target.append(nut_part)
			_target.append(washer_part)
			apex.attribute.assignMaterial(material = bolt_mat, target = _target )
			
			
			
			
			
			
			# use bolt_dimensions and orientation vector to check for duplicates
		
			
			MasterExists=False
					
			test_tuple= ( diam,head_height,head_diam,round(vect[0],3),round(vect[1],3),round(vect[2],3) )
			
			# Calculate Mesh Size from Bolt diamter
			NoOfElementsOnBoltPerimeter=8
			
			mesh_size=3.141*diam/NoOfElementsOnBoltPerimeter
			

			if Nut:
				# check for hex_flange_nut
				if Nut[0]=="hex_flange":
					nut_tuple=tuple([Nut[0],round(float(Nut[1]),2),round(float(Nut[1]),2),round(float(Nut[1]),2) ] )
				else:	
					nut_tuple= tuple([round(float(item),2) for item in Nut ])
			else:
				nut_tuple=(False,)
			if WasherHead:
				washer_head_tuple= tuple([round(float(item),2) for item in WasherHead ])
			else:
				washer_head_tuple=(False,)
			if WasherNut:
				washer_nut_tuple= tuple([round(float(item),2) for item in WasherNut ])
			else:
				washer_nut_tuple=(False,)
				
			test_tuple= (name,type) + nut_tuple + washer_head_tuple + washer_nut_tuple +  ( round(vect[0],3),round(vect[1],3),round(vect[2],3) )
			
			if test_tuple in EntityMasterD.keys():
				#if ( diam,head_height,head_diam,round(vect[0],3),round(vect[1],3),round(vect[2],3) ) in EntityMasterD.keys():
				#print("An existing instance of this connector is detected")
				instance_cnt+=1
				master_objs=EntityMasterD[test_tuple]
				MasterExists=True
			else:
				EntityMasterD[test_tuple]=[]
				orig_cnt+=1
				
			#else:
			#	bolt_masters=apex.entityCollection()
			#	EntityMasterD[( diam,head_height,head_diam,round(vect[0],3),round(vect[1],3),round(vect[2],3) )] = bolt_masters
			#bolt head and tip
			
			_tmp = apex.entityCollection()
			
			# Can not use Master-check with new part / assy paradigm
			MasterExists=False
			if MasterExists:
				#print("detected existing bolt... copy translate")
				old_solids=apex.entityCollection()
				
				old_solids.append(master_objs[0])
				old_solids.append(master_objs[1])
			
				# get transformation vector... this is the distance between p2 and the lower face of the bolt tip
				# get bolt tip location solid[1] as the face with longest centroid distance to the head solid[0] centroid
				
				ptA=old_solids[0].getCentroid()
				
				# need to find  faces with closest dist to 3p2
				tmp_faces= old_solids[1].getFaces()
				ptBL=[face.getCentroid() for face in tmp_faces]
				ptB_distL=[]
				for ptB in ptBL:
					tmp_delta= getDelta( [ptB.getX(),ptB.getY(),ptB.getZ()] , [ptA.getX(),ptA.getY(),ptA.getZ()] ) 
					ptB_distL.append(getLength(tmp_delta))
				# sort for shortest... get index of shorted to get the p2	
				indx=sorted(range(len(ptB_distL)), key=lambda k: ptB_distL[k])
				
				master_p2=ptBL[indx[-1]]
				
				p2p2_vect=getDelta( [master_p2.getX(),master_p2.getY(),master_p2.getZ()] , p2 ) 
				p2p2_dist=getLength(p2p2_vect)
				
				#print(p2p2_vect)
				#print(p2p2_dist)
				if MentatBolts == False:	
					bolt_head_out=transform_copy_solids(master_objs[0],p2p2_vect,p2p2_dist,copy=True)
					bolt_tip_out=transform_copy_solids(master_objs[1],p2p2_vect,p2p2_dist,copy=True)
					#
					bolt_halves=apex.entityCollection()
					bolt_halves.append(bolt_head_out)
					bolt_halves.append(bolt_tip_out)
					
					bolt_top=bolt_halves[0]
					bolt_tip=bolt_halves[1]
				else:
					bolt_head_out=transform_copy_solids(master_objs[0],p2p2_vect,p2p2_dist,copy=True)
					bolt_top=bolt_head_out
					bolt_tip=bolt_head_out
					highlight_stuff(bolt_tip,255,0,255)
				
				# translate copy objects to new location
				if WasherHead:
					washer_head_out=transform_copy_solids(master_objs[3],p2p2_vect,p2p2_dist,copy=True)
					#[bolt head, bolt_tip, nut, washer_head, washer_nut]
				if WasherNut:
					washer_nut_out=transform_copy_solids(master_objs[4],p2p2_vect,p2p2_dist,copy=True)
					#[bolt head, bolt_tip, nut, washer_head, washer_nut]
				if Nut:
					nut_out=transform_copy_solids(master_objs[2],p2p2_vect,p2p2_dist,copy=True)
					#[bolt head, bolt_tip, nut, washer_head, washer_nut]
				

			else:
				if WasherHead != False:
					if len(WasherHead) > 1:
						#Dimensions obtained from identification of CAD geometry
						#washer_height,washer_outer_dia,washer_inner_dia, getLength(p2pA)
						washer_height=float(WasherHead[0])
						washer_out_diam=float(WasherHead[2])
						washer_in_diam=float(WasherHead[1])
						washer_top_location=float(WasherHead[3])
						#
						washer_top_scale_fact=-1*washer_top_location/getLength(p1,p2)
						#print("washer_top_scale_fact: " + str(washer_top_scale_fact ))
						washer_start=addVectors([item*washer_top_scale_fact for item in vect],p2)
						washer_bot_scale_fact=-1*(washer_top_location-washer_height)/getLength(p1,p2)
						washer_end=addVectors([item*washer_bot_scale_fact for item in vect],p2) 
					else:
						#get washer dimensions from look up table
						washer_in_diam,washer_out_diam,washer_height=get_WasherDimensions(name)
						
						# p5 is the top face of the Washer Nut, if bolt data is derived from lookup table
						washer_scale_fact=washer_height/getLength(p1,p2)
						scale_vect=[item*washer_scale_fact for item in vect]
						p5=[p1[0] + scale_vect[0], p1[1] + scale_vect[1], p1[2] + scale_vect[2] ]
						washer_start=p5
						washer_end=p1
					
					#washer_head_out, washer_head_surf_out =CreateCylinder(p1,p5,washer_out_diam,washer_part,model_1)
					#washer_head_in,washer_head_surf_in=CreateCylinder(p1,p5,washer_in_diam,washer_part,model_1)
					
					#debug:
					#print("====washer_head====")
					#print(washer_start)
					#print(washer_end)
					#print(washer_out_diam)
					#print(washer_part)
					
					washer_head_out =CreateSolidRing(washer_start,washer_end,washer_out_diam,washer_in_diam,washer_part,model_1)
					
					#_tmp.append(washer_head_surf_out)
					#_tmp.append(washer_head_surf_in)
					
					# p1 need to be adjusted for washer head
					#p1=p5
					if mesh_size != None:
						washer_head_mesh=createHexMesh(washer_head_out, mesh_size)
					HeadWashers.append(washer_head_out)
					#
				# Stefan chnaged 20190114	
				#if WasherNut and Nut:
				if WasherNut:	
					if len(WasherNut) > 1:
						#print("WasherNut")
						#print(WasherNut)
						#print(len(WasherNut))
						#Dimensions obtained from identification of CAD geometry
						#washer_height,washer_outer_dia,washer_inner_dia, getLength(p2pA)
						washer_height=float(WasherNut[0])
						washer_out_diam=float(WasherNut[2])
						washer_in_diam=float(WasherNut[1])
						washer_nut_top_location=float(WasherNut[3])
						#
						washer_nut_top_scale_fact=-1*washer_nut_top_location/getLength(p1,p2)				
						washer_start=addVectors([item*washer_nut_top_scale_fact for item in vect],p2)
						washer_bot_scale_fact=-1*(washer_nut_top_location-washer_height)/getLength(p1,p2)
						washer_end=addVectors([item*washer_bot_scale_fact for item in vect],p2)
						
					else:
						#print(WasherNut)
						#print(len(WasherNut))
						#get washer dimensions from look up table
						washer_in_diam,washer_out_diam,washer_height=get_WasherDimensions(name)
					
						washer_scale_fact=washer_height/getLength(p1,p2)
						scale_vect=[item*washer_scale_fact for item in vect]
						p6=[p2[0] - scale_vect[0], p2[1] - scale_vect[1], p2[2] - scale_vect[2] ]
						washer_start = p6
						washer_end= p2
								
					washer_nut_out =CreateSolidRing(washer_start,washer_end,washer_out_diam,washer_in_diam,washer_part,model_1)
					
			
					if mesh_size != None:
						washer_nut_mesh=createHexMesh(washer_nut_out, mesh_size)
					NutWashers.append(washer_nut_out)
					#
					#TouchObjects.append(washer_nut_out)	
				
				# create stem cylinder
				stem,stem_surf=CreateCylinder(p1,p2,diam,bolt_part,model_1)
				_tmp.append(stem_surf)
				#print("stem: " + stem.name)
				

				# create bolt head
				vect=getDelta(p1,p2)
				if type=="hex_flange":

					# flange
					head_scale_fact=head_flange_height/getLength(p1,p2)
					scale_vect=[item*head_scale_fact for item in vect]
					#
					p3=[p1[0] - scale_vect[0], p1[1] - scale_vect[1], p1[2] - scale_vect[2] ]
					head,head_surf=CreateCylinder(p1,p3,head_flange_dia,bolt_part,model_1)
					print("Head surf created: " + head_surf.getName() )

					# head
					head_scale_fact=head_height/getLength(p1,p2)
					scale_vect=[item*head_scale_fact for item in vect]
					#
					
					# get new p1... (the previous P3)
					p1new=p3
					
					#p3new=[p1[0] - scale_vect[0], p1[1] - scale_vect[1], p1[2] - scale_vect[2] ]
					p3new=[p1new[0] - scale_vect[0], p1new[1] - scale_vect[1], p1new[2] - scale_vect[2] ]
					
					
					head_top,head_top_surf=CreateCylinder(p1new,p3new,head_diam,bolt_part,model_1)
					print("Head Top surf created: " + head_top_surf.getName() )
					_tmp.append(head_top_surf)
					# combine head flange and head top to one solid
					
					_target = apex.EntityCollection()
					_target.append( head_top )
					_target.append( head )
					result = apex.geometry.mergeBoolean(
						target = _target,
						retainOriginalBodies = False,
						mergeSolidsAsCells = False
					)
					head=result[0]
					
					# split head with head-top cylindrical face to avoid floating splits
					# split face is the cylinder with area: head_diam*pi*dist(p1new,p3new)
					height=getLength(p1new,p3new)
					A0=head_diam*3.141*height
					
					for tmp_face in head.getFaces():
						tmpA=tmp_face.getArea()
						if abs(tmp_face.getArea() - A0 ) < 1.0 :
							split_face=tmp_face
							break
							
					#slice_solid_partition(head, split_face)
					
					_target = apex.entityCollection()
					_target.append( head )
					_face = apex.entityCollection()
					_face.append( split_face )
					result = apex.geometry.splitOnSurface(
						target = _target,
						face = _face,
						splitBehavior = apex.geometry.GeometrySplitBehavior.Partition
					)
					
				elif type=="countersunk":
					# assume countersunk angle is 45 deg.
					# diameter and head height determine if head is just a cone or a cone + cylinder
					# if more that 20% of rivet is cylindrical- add cylinder.
					# for example type 204
					
					delta=0.5*(head_diam -diam)
					# create cone,
					head_scale_fact=delta/getLength(p1,p2)
					scale_vect=[item*head_scale_fact for item in vect]
					new_p3=[p1[0] - scale_vect[0], p1[1] - scale_vect[1], p1[2] - scale_vect[2] ]
					head, head_surf=CreateCone(p1,new_p3,diam,head_diam,bolt_part,model_1)
					
					if (head_height-delta)/delta > 0.2:
						# create cylindrical part if head is heigh enough 
						new_p1=new_p3
						head_scale_fact=head_height/getLength(p1,p2)
						scale_vect=[item*head_scale_fact for item in vect]
						new_p3=[p1[0] - scale_vect[0], p1[1] - scale_vect[1], p1[2] - scale_vect[2] ]
						head_top, head_top_surf=CreateCylinder(new_p1,new_p3,head_diam,bolt_part,model_1)
						
						
						#head_top,head_top_surf=CreateCylinder(p1new,p3new,head_diam,bolt_part,model_1)
						print("Head Top surf created: " + head_top_surf.getName() )
						_tmp.append(head_top_surf)
						# combine head cone and head cyl  to one solid
						print("Head", head.getName() )
						print("Head Top", head_top.getName() )
						
						
						_target = apex.EntityCollection()
						_target.append( head_top )
						_target.append( head )
						result = apex.geometry.mergeBoolean(
							target = _target,
							retainOriginalBodies = False,
							mergeSolidsAsCells = True
						)
						head=result[0]
						
					

						# # flange
						# head_scale_fact=head_flange_height/getLength(p1,p2)
						# scale_vect=[item*head_scale_fact for item in vect]
						# #
						# p3=[p1[0] - scale_vect[0], p1[1] - scale_vect[1], p1[2] - scale_vect[2] ]
						# head,head_surf=CreateCylinder(p1,p3,head_flange_dia,bolt_part,model_1)
						# print("Head surf created: " + head_surf.getName() )

						# # head
						# head_scale_fact=head_height/getLength(p1,p2)
						# scale_vect=[item*head_scale_fact for item in vect]
						# #
						
						# # get new p1... (the previous P3)
						# p1new=p3
						
						# #p3new=[p1[0] - scale_vect[0], p1[1] - scale_vect[1], p1[2] - scale_vect[2] ]
						# p3new=[p1new[0] - scale_vect[0], p1new[1] - scale_vect[1], p1new[2] - scale_vect[2] ]
						
						

						if 1==0:
							# split head with head-top cylindrical face to avoid floating splits
							# split face is the cylinder with area: head_diam*pi*dist(p1new,p3new)
							height=getLength(new_p1,new_p3)
							A0=head_diam*3.141*height
							
							for tmp_face in head.getFaces():
								tmpA=tmp_face.getArea()
								if abs(tmp_face.getArea() - A0 ) < 1.0 :
									split_face=tmp_face
									break
									
							#slice_solid_partition(head, split_face)
							
							_target = apex.entityCollection()
							_target.append( head )
							_face = apex.entityCollection()
							_face.append( split_face )
							result = apex.geometry.splitOnSurface(
								target = _target,
								face = _face,
								splitBehavior = apex.geometry.GeometrySplitBehavior.Partition
							)		
					
					
					
					
					
				elif type=="TX":
					head_scale_fact=head_height/getLength(p1,p2)
					scale_vect=[item*head_scale_fact for item in vect]
					#
					p3=[p1[0] - scale_vect[0], p1[1] - scale_vect[1], p1[2] - scale_vect[2] ]
					head,head_surf=CreateTXHead(p1,p3,head_diam,diam,bolt_part,model_1)
					
					#diam=M_temp[0]
					#head_height=M_temp[1]
					#head_diam=M_temp[2]
					#slot_dia=M_temp[3]
					# create subtract cylinder
					
				elif type=="pin":
					print("No head on pin")
					head_surf=CreateCircleAtPt(p1,p2,1.2*diam,bolt_part,model_1)
					
					
					
				else:
					head_scale_fact=head_height/getLength(p1,p2)
					scale_vect=[item*head_scale_fact for item in vect]
					#
					p3=[p1[0] - scale_vect[0], p1[1] - scale_vect[1], p1[2] - scale_vect[2] ]
					head,head_surf=CreateCylinder(p1,p3,head_diam,bolt_part,model_1)
					#print("head " + head.name)
				
				
				# need to boolean add stem and head
				
				
				# create nut
				if Nut != False:
					if len(Nut) > 1:
						#Dimensions obtained from identification of CAD geometry or from table (hex_flange)
						#washer_height,washer_outer_dia,washer_inner_dia, getLength(p2pA)
						if Nut[0] == 'hex_flange':
							#Nut Inner diameter, need to be same as Bolt outer diameter	=> ensured by name (m10,m14 et.c.)
							#nut_in_diam, nut_out_diam, nut_dia, nut_height, tot_height=get_NutDimension(name,'hex_flange')
							nut_in_diam, head_flange_height, nut_out_diam, tot_height, nut_dia = get_NutDimension(name,'hex_flange')
							
							nut_height=tot_height-head_flange_height
							print("==Output from get_NutDimension===")
							print("nut_in_diam  ",nut_in_diam)
							print("nut_out_diam  ",nut_out_diam)
							print("nut_dia  ",nut_dia)
							print("nut_height  ",nut_height)
							print("tot_height  ",tot_height)
							
							
							
							nut_top_location=float(Nut[3])
							nut_top_scale_fact=-1*nut_top_location/getLength(p1,p2)				
							nut_start=addVectors([item*nut_top_scale_fact for item in vect],p2)
							#nut_bot_scale_fact=-1*(nut_top_location-nut_height)/getLength(p1,p2)
							nut_bot_scale_fact=-1*(nut_top_location-tot_height)/getLength(p1,p2)
							nut_end=addVectors([item*nut_bot_scale_fact for item in vect],p2)
							#
							#p11=nut_start
							#p11_point=apex.geometry.createPointXYZ(p11[0],p11[1],p11[2])
							#highlight_stuff(p11_point,255,0,50)
							#text1=apex.display.displayText("nut_start",p11_point)
							#p22=nut_end
							#p22_point=apex.geometry.createPointXYZ(p22[0],p22[1],p22[2])
							#highlight_stuff(p22_point,255,0,50)
							#text1=apex.display.displayText("nut_end",p22_point)
						
						else:
							nut_height=float(Nut[0])
							
							# Nut Inner diameter, nned to be same as Bolt outer diameter
							#nut_out_diam=float(Nut[1])
							nut_out_diam=float(Nut[2])
							nut_in_diam=diam
							#nut_in_diam=diam
							
							nut_top_location=float(Nut[3])
							nut_top_scale_fact=-1*nut_top_location/getLength(p1,p2)				
							nut_start=addVectors([item*nut_top_scale_fact for item in vect],p2)
							nut_bot_scale_fact=-1*(nut_top_location-nut_height)/getLength(p1,p2)
							nut_end=addVectors([item*nut_bot_scale_fact for item in vect],p2)

					else:
						p4=[p2[0] + scale_vect[0], p2[1] + scale_vect[1], p2[2] + scale_vect[2] ]
						nut_start=p2
						nut_end=p4
						
					#nut_out,nut_surf_out=CreateCylinder(nut_start,nut_end,nut_out_diam,nut_part,model_1)
					#nut_in,nut_surf_in=CreateCylinder(nut_start,nut_end,nut_in_diam,nut_part,model_1)
					
					if Nut[0] == 'hex_flange':
						# 	nut_in_diam, nut_out_diam, nut_dia, nut_height, tot_height
						
						print("head_height",head_height)
						print("head_flange_height",head_flange_height)

						nut_out = create_HexFlangeNut(nut_start,nut_end,nut_out_diam,head_flange_height,head_height,nut_dia,nut_in_diam,nut_part,model_1)
					else:
						nut_out =CreateSolidRing(nut_start,nut_end,nut_out_diam,nut_in_diam,nut_part,model_1)
						
					#nut,nut_surf=CreateCylinder(p2,p4,head_diam,bolt_part,model_1)
					#print("nut " + nut_out.name)
					if mesh_size != None:
						nut_mesh=createHexMesh(nut_out, mesh_size)
					Nuts.append(nut_out)

				if mesh_size != None:
					#splitLocation="",splitPercentage=0,splitDistance=0
					# parameters from new GUI
					#splitLocation="Above Nut"
					#split_percentage=90

					#get a surf at bolt mid point
					# split at bolt halves
					#mid_surf=transform_copy_surf(head_surf,getDelta(p1,p2),0.5*getLength(p1,p2),copy=True)
					#"Split between head and nut", "Split right under Head", "Split above Nut", "Split at Percentage", "Split at Distance"#
	
					#Add parametric splif of Bolt:
					if splitLocation=="Split between head and nut":
						split_param=0.5
					elif splitLocation=="Split right under Head":
						split_param=0.1
					elif splitLocation=="Split above Nut":
						split_param=1
						# split just under the bolt
					elif splitLocation=="Split at Percentage from Head":
						split_param=splitPercentage*0.01
						
					elif splitLocation=="Split at Distance from Head":
						split_param=splitDistance/getLength(p1,p2)

					else:
						split_param=0.5
					
					print("Split Location",splitLocation,"Split Param",split_param)


					#split between head and washer/ nut
					if Nut == False and WasherNut == False:
						print("No nut and no Nut Washer")
						print("No nut, split at midpoint")
						mid_surf=transform_copy_surf(head_surf,getDelta(p1,p2),split_param*getLength(p1,p2),copy=True)
					elif WasherNut != False:
						print("Nut Washer exists: split between head and nut washer")
						print(washer_nut_top_scale_fact)
						mid_surf=transform_copy_surf(head_surf,getDelta(p1,p2),split_param*(1+washer_nut_top_scale_fact)*getLength(p1,p2),copy=True)
					elif Nut != False:
						print("Nut exist: split between head an nut")
						print(nut_top_scale_fact)
						mid_surf=transform_copy_surf(head_surf,getDelta(p1,p2),split_param*(1+nut_top_scale_fact)*getLength(p1,p2),copy=True)
					else:
						print("what is going on...")
						print(Nut)
						print(WasherNut)
						print(WasherHead)
						mid_surf=transform_copy_surf(head_surf,getDelta(p1,p2),split_param*getLength(p1,p2),copy=True)


					if type != "pin":
						# combine stem and head to one solid
						print("Head: " + head.getName())
						print("Stem: " + stem.getName())
						_target = apex.EntityCollection()
						_target.append( head )
						_target.append( stem )
						result = apex.geometry.mergeBoolean(
							target = _target,
							retainOriginalBodies = False,
							#change 15/2
							mergeSolidsAsCells = False
						)
						stem=result[0]
					


					#finally delete head_surf
					_tmp.append(head_surf)
					
					# add the created connector to MasterDict
					# for non existing objects, use solid[1] as dummy place holder object
					# [bolt head, bolt_tip, nut, washer_head, washer_nut]
					# add proper bolt data!!
					#key_tuple=( diam,head_height,head_diam,round(vect[0],3),round(vect[1],3),round(vect[2],3) )
					key_tuple=test_tuple
					#print("key_tuple")
					#print(key_tuple)
					

					# Common mesh for SOl400 and Mentat- assign dummy material to bolt tip
					
					# mesh before split to keep mesh congruency
					solid_to_mesh_objects=apex.EntityCollection()
					solid_to_mesh_objects.append(stem)
					
					if type == "pin":
						#bolt_mesh=createHexMesh(stem, mesh_size)
						#bolt_halves=slice_solid_split(stem, mid_surf)
						bolt_halves=slice_solid_partition(stem, mid_surf)
						
						# tip is the part with the nut
						
						
						if split_param <= 0.5:
							bolt_top=bolt_halves[0]
							bolt_tip=bolt_halves[1]
						else:
							bolt_top=bolt_halves[1]
							bolt_tip=bolt_halves[0]
						# Tip is yellow
						# Top is magenta
						highlight_stuff(bolt_tip,255,255,0)
						highlight_stuff(bolt_top,255,0,255)
							
						
							
					else:
						bolt_halves=slice_solid_partition(stem, mid_surf)
						#bolt_tip is the part with largest distance to head_surf
						pp0= bolt_halves[0].getCentroid()
						pp1= bolt_halves[1].getCentroid()
						hs=head_surf.getCentroid()
						v0=[ hs.getX()-pp0.getX(),hs.getY()-pp0.getY(),	hs.getZ()-pp0.getZ() ]
						v1=[ hs.getX()-pp1.getX(),hs.getY()-pp1.getY(),	hs.getZ()-pp1.getZ() ]
						delta0=getLength(v0)
						delta1=getLength(v1)
						if delta0 < delta1:
							bolt_top=bolt_halves[0]
							bolt_tip=bolt_halves[1]
						else:
							bolt_top=bolt_halves[1]
							bolt_tip=bolt_halves[0]
							
						print("Head surf: " + head_surf.getName() )

						# Change 15/2
						#print("bolt_top",bolt_top.getName())
						
						print("Bolt_top_id", bolt_top.getId())
						
						
						if type!="hex_flange":
							try:
								bolt_cells=slice_solid_partition2(bolt_top, head_surf)
								_tmp.append(head_surf)			
							except:
								print("fail slice of bolt top ")
						
						else:
							try:
								bolt_cells=slice_solid_partition2(head, head_surf)
								#bolt_cells=slice_solid_partition2(bolt_top, head_surf)
								_tmp.append(head_surf)			
							except:
								print("fail slice of bolt head")
					
					# This applies to both pin and bolt types	
					if 1==1:	
						bolt_mesh=createHexMesh(stem, mesh_size)
					
						# Tip is yellow
						# Top is magenta
						
						#highlight_stuff(bolt_top,255,0,255)
						try:
							for bolt_cell in bolt_cells:
								highlight_stuff(bolt_cell,255,0,255)
						except:
							print("Warning: Could not access bolt_cell for highlight")
						highlight_stuff(bolt_tip,255,255,0)	

						# assign Material: _preload_dummy_material
						
						try:
							_dummy_material = apex.catalog.getMaterial(name = "_preload_dummy_material" )
						except:
							print("Creating material: _preload_dummy_material")
							_dummy_material = apex.catalog.createMaterial(
								name = "_preload_dummy_material",
								description = "",
								color = [ 64, 254, 250 ]
							)

							_dummy_material.update(elasticModulus = 205000.,)
							_dummy_material.update(poissonRatio = 0.29,)
							_dummy_material.update(density = 0.0000078,)
							
						tmp_entities=apex.entityCollection()
						tmp_entities.append(bolt_tip)
							

						AssignMaterial(tmp_entities,_dummy_material)
					
					# Assign dummy 

					BoltHeads.append(bolt_top)
					BoltTips.append(bolt_tip)
							
					#else:
					#	bolt_top=bolt_halves[0]
					#	bolt_tip=bolt_halves[1]
						
						


					_tmp.append(mid_surf)

					
					EntityMasterD[key_tuple].append(bolt_top)
					EntityMasterD[key_tuple].append(bolt_tip)
					
					if Nut:
						EntityMasterD[key_tuple].append(nut_out)
					else:
						EntityMasterD[key_tuple].append(bolt_tip)
						
					if WasherHead:
						EntityMasterD[key_tuple].append(washer_head_out)
					else:
						EntityMasterD[key_tuple].append(bolt_tip)
					if WasherNut:
						EntityMasterD[key_tuple].append(washer_nut_out)

					else:
						EntityMasterD[key_tuple].append(bolt_tip)	

			# end check MasterFlag
			
			# Add preload info to bolt
			bolt_top.getBody().addUserAttribute(userAttributeName ="PreLoad", stringValue = preLoad )

			
			#===========Start Glue====================
			#print("I am in glue")
			#print("Len of bolt_halves:" + str(len(bolt_halves)))
			# glue bolt halves
			
			
			
			if 1==0:
				bolt_bolt_glue = apex.attribute.createMeshIndependentTie(
					target = bolt_halves,
					#tiePairTolerance = 0.1,
					tiePairTolerance = boltTopTouchTolerance,
					calculatePatchToleranceAutomatically = True
				)
				#print("Len of tie:" + str(len(bolt_bolt_glue)))
				#print(bolt_top.getName())
				#print(bolt_tip.getName())
				#highlight_stuff(bolt_top,255,255,0)
				#highlight_stuff(bolt_tip,255,0,255)
				try:
					#print("Glue: " + bolt_bolt_glue[0].getName() )
					_GlueL.append(bolt_bolt_glue[0].getName())
					_GlueL2.append(bolt_bolt_glue[0].getPathName())
				except:
					print("Bolt halves Glue failed")
			
			
			# if WasherHead , create contact between Head and Washer
			# assume bolt_top, will always be top part after split

			#Create Touch bewteen bolt head and washer head
			if WasherHead:
				print("Washer Head",washer_head_out.getName())
				tmp_list= apex.entityCollection()
				tmp_list.append(washer_head_out)
				highlight_stuff(washer_head_out,0,0,255)
				#tmp_list.append(bolt_top)
				#tmp_list.extend(bolt_cells)
				tmp_list.append(stem)
				#print("****")
				#for a_item in tmp_list:
				#	print(a_item.getName() )
				#print("****")
				try:
					washer_head_glue = apex.attribute.createMeshIndependentTie(
						target = tmp_list,
						tiePairTolerance = 0.1,
						calculatePatchToleranceAutomatically = True
					)
					#print("##### GLUE ############")
					#print("Path: " + washer_head_glue[0].getName() )
					#print("Name: " + washer_head_glue[0].getName() )
					#print("pathName: " + washer_head_glue[0].getName() )
					#if len(washer_head_glue) > 0:
					_TouchL.append(washer_head_glue[0].getName())
					_TouchL2.append(washer_head_glue[0].getPathName())
				except:
					print("***Warning failed to glue " + washer_head_out.getName() )
				#else:
				#	print("No contact found ")
				# If there is a head washer, it will contact other objects
				TouchObjects.append(washer_head_out)
				
			#else:
			#	# No Washer, this means bolt Head will touch other bodies
			#	TouchObjects.append(bolt_top)
				
			#Always add bolt head to Touch candidates, unless pin... then top is threaded
			if type == "pin":
				# currently only supporting pins with one nut
				# the other (threaded part) is the top and is added to Glue Objects
				#GlueObjects.append(bolt_top)
				GlueObjects.append(stem)
			else:
				#TouchObjects.append(bolt_top)
				TouchObjects.append(stem)		

			#Create Glue bewteen bolt and nut	
			if Nut:
				tmp_list= apex.entityCollection()
				tmp_list.append(nut_out)
				highlight_stuff(nut_out,0,255,0)
				#tmp_list.append(bolt_tip)
				# manage later to allow only bolt tip
				tmp_list.append(stem)
				try:
					nut_glue = apex.attribute.createMeshIndependentTie(
						target = tmp_list,
						tiePairTolerance = 0.1,
						calculatePatchToleranceAutomatically = True
					)
					#print("Glue: " + nut_glue[0].getName() )
					_GlueL.append(nut_glue[0].getName())
					_GlueL2.append(nut_glue[0].getPathName())
				except:
					print("***Warning failed to glue " + nut_out.getName() )
			# Create Touch between Nut and Washer
			if WasherNut:
				tmp_list= apex.entityCollection()
				tmp_list.append(washer_nut_out)
				highlight_stuff(washer_nut_out,255,0,0)
				if Nut:
					try:
						tmp_list.append(nut_out)
						washer_nut_glue = apex.attribute.createMeshIndependentTie(
							target = tmp_list,
							tiePairTolerance = 0.1,
							calculatePatchToleranceAutomatically = True
						)
						#print("Touch: " + washer_nut_glue[0].getName() )
						_TouchL.append(washer_nut_glue[0].getName())
						_TouchL2.append(washer_nut_glue[0].getPathName())
					except:
						print("***Warning failed to glue " + washer_nut_out.getName() )
						
				# If there is a nut washer, it will contact other objects
				TouchObjects.append(washer_nut_out)
				
			else:
				if Nut:
					TouchObjects.append(nut_out)	
				else:
					#if there is also no nut, glue the bolt tip to watever is close
					#assuming there is a threaded hole /that is not identified as a nut
					
					#GlueObjects.append(bolt_tip)
					GlueObjects.append(stem)
		
			# delete temporary surfaces	
			if len(_tmp) != 0:
				apex.deleteEntities(_tmp)

			tie_count=0
			candidate_nbr=0
			candidate_hitL=[]
			
			try:
				#print(len(n_list[0:5]))
				slask=len(n_list[0:5])
			except:
				#no n_list exists, create one
				n_list=list(range(len(TouchCandidates)))
				# n_list=[0,1,2,3,4...]

			for n in n_list:
				#for candidate in TouchCandidates:
				tmp_list= apex.entityCollection()
				tmp_list+=TouchObjects
				tmp_list.append(TouchCandidates[n])
				try:
					tmp_glue = apex.attribute.createMeshIndependentTie(
						target = tmp_list,
						#tiePairTolerance = 0.1,
						tiePairTolerance = boltTopTouchTolerance,
						calculatePatchToleranceAutomatically = True
					)
							
					#print("Touch: " + tmp_glue[0].getName() )
					for tmp_glue_item in tmp_glue:
						_TouchL.append(tmp_glue_item.getName())
						_TouchL2.append(tmp_glue_item.getPathName())
						tie_count +=1
						candidate_hitL.append(n)
						#highlight_stuff(TouchCandidates[n],0,206,209)
					
					#if len(tmp_glue) > 1:
					#	print("########Touch#####" + str(len(tmp_glue)))
					
					
					# always need to find all potential contacts
					
					#if tie_count==len(TouchObjects):
					#	print("found all " + str(len(TouchObjects)))
					#	break		
				except:
					#print("No tie creted")
					pass
					
			#resequence the TouchCandidates list so that items that gave a hit is moved to the top
			
			# no meaning to resequence as bolt is always checked
			
			#for hit_n in candidate_hitL:
			#	n_list.remove(hit_n)
			#	n_list.insert(0, hit_n)
				
			
			if len(GlueObjects) > 0:
				for candidate in TouchCandidates:
					tmp_list= apex.entityCollection()
					tmp_list+=GlueObjects
					tmp_list.append(candidate)
					try:
						tmp_glue = apex.attribute.createMeshIndependentTie(
							target = tmp_list,
							#tiePairTolerance = 0.5,
							tiePairTolerance = boltTipGlueTolerance,
							calculatePatchToleranceAutomatically = True
						)
					except:
						print("Failed to glue: " + candidate.getName() )
					
					try:						
						#print("Glue: " + tmp_glue[0].getName() )
						for tmp_glue_item in tmp_glue:
							_GlueL.append(tmp_glue_item.getName())
							_GlueL2.append(tmp_glue_item.getPathName())
							#highlight_stuff(candidate,255,255,0)
						
						#if len(tmp_glue) > 1:
						#	print("########GLue#####" + str(len(tmp_glue)))
						
					except:
						print("No tie creted " + candidate.getName() )
						pass
					
	# End looping BoltDict
	
	outBoltD={}
	outBoltD['BoltHeads']=BoltHeads
	outBoltD['BoltTips']=BoltTips
	outBoltD['HeadWashers']=HeadWashers
	outBoltD['NutWashers']=NutWashers
	outBoltD['Nuts']=Nuts
	outBoltD['GlueL'] = _GlueL
	outBoltD['TouchL'] = _TouchL
	outBoltD['GlueL2'] = _GlueL2
	outBoltD['TouchL2'] = _TouchL2
	
	
	# Rename the touch items
	# Assign Contact behaviour as custom attributes 
	# Changed 16 Dec 2020
	#_contact_tol=TouchTol
	
	
	for item in _TouchL2:
		print(item)
		new_name= item.split('/')[-1] + "_T"
		new_name=new_name.replace("ä","ae").replace("ö","oe").replace("å","aa").replace("ü","ue").replace("ß","ss")
		new_name=new_name.replace("Ä","ÄE").replace("Ö","OE").replace("Å","AA").replace("Ü","UE")
		
		print(new_name)
		
		
		#try:
		if 1==1:
			meshIndependentTie_ = apex.attribute.getMeshIndependentTie( pathName = item )
			print(meshIndependentTie_.getName())
			meshIndependentTie_.update(
				name = new_name,
				patchToleranceCalculationMethod = apex.AutoManual.Manual,
				tiePatchTolerance = boltTopTouchTolerance,
			)
			print("Renamed: " + new_name)
			meshIndependentTie_.addUserAttribute(userAttributeName ="contactType", stringValue = "Touch" )
			#
			meshIndependentTie_.addUserAttribute(userAttributeName ="Friction", stringValue = str(boltFriction) )
			meshIndependentTie_.addUserAttribute(userAttributeName ="StressFreeInitial", stringValue = "True" )
			meshIndependentTie_.removeUserAttributes(userAttributeNames =  [ "RetainGapOverlaps" ] ) 
			meshIndependentTie_.addUserAttribute(userAttributeName ="Tolerance", stringValue = str(boltTopTouchTolerance) )
			meshIndependentTie_.addUserAttribute(userAttributeName ="Bias", stringValue = "0.95" )



		#except:
		#	print("***Rename and custom attribute Failed***")

	# Rename the glued meshIndependentTie_s
	
	print("No of Glue: " + str(len(_GlueL2)) )
	#_contact_tol=GlueTol
	for item in _GlueL2:
		
		new_name= item.split('/')[-1] + "_G"
		
		new_name=new_name.replace("ä","ae").replace("ö","oe").replace("å","aa").replace("ü","ue").replace("ß","ss")
		new_name=new_name.replace("Ä","ÄE").replace("Ö","OE").replace("Å","AA").replace("Ü","UE")
			#
		
		print(new_name)
		try:
		#if 1==1:
			
			meshIndependentTie_ = apex.attribute.getMeshIndependentTie( pathName = item )
			meshIndependentTie_.update(
				name = new_name,
				patchToleranceCalculationMethod = apex.AutoManual.Manual,
				tiePatchTolerance = boltTipGlueTolerance,
			)
			print("Renamed: " + new_name)
			#
			meshIndependentTie_.addUserAttribute(userAttributeName ="contactType", stringValue = "Glue" )
			meshIndependentTie_.addUserAttribute(userAttributeName ="RetainGapOverlaps", stringValue = "True" )
			meshIndependentTie_.removeUserAttributes(userAttributeNames =  [ "Friction", "StressFreeInitial" ] ) 
			meshIndependentTie_.addUserAttribute(userAttributeName ="Tolerance", stringValue = str(boltTipGlueTolerance) )
			meshIndependentTie_.addUserAttribute(userAttributeName ="Bias", stringValue = "0.95" )
			
		except:
			print("***Rename Failed***")

		
	#meshIndependentTie_ = apex.attribute.getMeshIndependentTie( pathName = 'Model/MountAssembly/Glue/Glue 2#T' )
	#meshIndependentTie_.update(
	#	name = "Glue 2#T"
	#)
	
	tmp_entities=apex.entityCollection()
	#tmp_entities+=BoltHeads
	#tmp_entities+=BoltTips
	#tmp_entities+=HeadWashers
	#tmp_entities+=NutWashers
	#tmp_entities+=Nuts

	
	#try:
	#	_material = apex.catalog.getMaterial(name = bolt_mat_name)
	#	AssignMaterial(tmp_entities,_material)
	#except:
	#	print("Could not assign default material to bolts, washers and nuts")
		
	if 1==0:
		for key in outBoltD.keys():
			print(key)
			print(outBoltD[key])
			try:
				CheckObject(outBoltD[key])
			except:
				print("pass")
			print("===")
	
	print("=========================")
	tot_bolts=instance_cnt + orig_cnt
	print("Created " + str(tot_bolts) + " 3d-bolts")
	print(str(instance_cnt) + " created as copies")
	
	return	outBoltD

def CheckObject(obj):
	try:
		print("Length: " +  str(len(obj)))
		# object is a list, continue checks with first item in the list
		tmp_obj=obj[0]
	except TypeError:
		print("Not a list")
		tmp_obj=obj	
	try:
		print("Name: " + tmp_obj.getName())
	except:
		print("getName() failed")
		
	try:
		print("Path: " + tmp_obj.getPath())
	except:
		print("getPath() failed")

	try:
		print("Id: " + tmp_obj.getId())
	except:
		print("getId() failed")

def createHexMesh(solid, mesh_size):
	_target = apex.entityCollection()
	_target.append( solid )
	_SweepFace = apex.entityCollection()
	result = apex.mesh.createHexMesh(
		name = "",
		target = _target,
		meshSize = mesh_size,
		surfaceMeshMethod = apex.mesh.SurfaceMeshMethod.Auto,
		mappedMeshDominanceLevel = 2,
		elementOrder = apex.mesh.ElementOrder.Linear,
		refineMeshUsingCurvature = False,
		elementGeometryDeviationRatio = 0.10,
		elementMinEdgeLengthRatio = 0.20,
		sweepFace = _SweepFace,
		hexMeshMethod = apex.mesh.HexMeshMethod.Auto
	)
	return result

def createHexMesh2(solids, mesh_size):
	
	
	# In order to get continuous mesh over the split, we first need to mesh the cells relating to the stem
	# then the head cells
	cells=apex.entityCollection()
	for solid in solids:
		cells+=solid.getCells()
		cell_volumeL=[]
	for cell in cells:
		cell_volumeL.append(cell.getVolume())
	indx=sorted(range(len(cell_volumeL)), key=lambda k: cell_volumeL[k])

	if abs(cell_volumeL[indx[0]] - cell_volumeL[indx[1]]) < 1:
		#print("first two cells are equal")
		pass
	elif abs(cell_volumeL[indx[-2]] - cell_volumeL[indx[-1]]) < 1:
		#print("last two cells are equal")
		indx.reverse()
	else:
		#print("no match")
		print(str(cell_volumeL[indx[0]] - cell_volumeL[indx[1]]))
		print(str(cell_volumeL[indx[-2]] - cell_volumeL[indx[-1]]))
	
	_target = apex.entityCollection()
	_target.append(cells[indx[0]])
	_target.append(cells[indx[1]])
	_SweepFace = apex.entityCollection()
	result = apex.mesh.createHexMesh(
		name = "",
		target = _target,
		meshSize = mesh_size,
		surfaceMeshMethod = apex.mesh.SurfaceMeshMethod.Auto,
		mappedMeshDominanceLevel = 2,
		elementOrder = apex.mesh.ElementOrder.Linear,
		refineMeshUsingCurvature = False,
		elementGeometryDeviationRatio = 0.10,
		elementMinEdgeLengthRatio = 0.20,
		sweepFace = _SweepFace,
		hexMeshMethod = apex.mesh.HexMeshMethod.Auto
	)
	# mesh remaining cells
	_target = apex.entityCollection()
	
	
	#_target.append(cells[indx[2]])
	n=2
	while n < len(indx):
		_target.append(cells[indx[n]])
		n+=1

	_SweepFace = apex.entityCollection()
	result = apex.mesh.createHexMesh(
		name = "",
		target = _target,
		meshSize = mesh_size,
		surfaceMeshMethod = apex.mesh.SurfaceMeshMethod.Auto,
		mappedMeshDominanceLevel = 2,
		elementOrder = apex.mesh.ElementOrder.Linear,
		refineMeshUsingCurvature = False,
		elementGeometryDeviationRatio = 0.10,
		elementMinEdgeLengthRatio = 0.20,
		sweepFace = _SweepFace,
		hexMeshMethod = apex.mesh.HexMeshMethod.Auto
	)
	
	
	
	
	
	return result

	
#def GetAttributes(object,attr_list):
#	n_solid.addUserAttribute(userAttributeName ="zoom_status", stringValue = "copy" )

		
def ReadBoltCurves(bolt_curves):
	#print("Processing ReadBoltCurve")
	#tmp_parts=model.getParts(True)
	# get all curves from model
	#bolt_curves = apex.entityCollection()
	#for tmp_part in tmp_parts:
	#	bolt_curves += tmp_part.getCurves()
	
	print("There are : " + str(len(bolt_curves)) + " bolt curves selected")
	
	BoltD={}
	#bolt_curves=bolt_curve_part.getCurves()
	
	
	
	#attrib_names=['dim','type','washer_head','nut','washer_nut','bolt_head_diam','head_height']
	attrib_names=['dim','type','washer_head','nut','washer_nut']
	for curve in bolt_curves:
		GoFlag=True
		tmp_attributes=curve.getUserAttributes(attrib_names)
		attD={}
		# check if there is a dim in format 'm10' if not skip bolt
		# if type is not specified it will take default value 'hex' 
		# all other attributes will take default value False if not set
		for attrib in tmp_attributes:
			attD[attrib.getAttributeName()]=attrib.getStringValue()
		if 'dim' in attD.keys():
			dim=attD['dim']
		else:
			print("***Error no bolt dimension given")
			GoFlag=False
		

		if GoFlag:
			if 'type' in attD.keys():
				type=attD['type']
			else:
				type='hex'
				
			if 'nut' in attD.keys():
				if attD['nut'].upper() == "YES" or attD['nut'].upper() == "TRUE":
					nut=True
				elif attD['nut'].upper() == "NO" or attD['nut'].upper() == "FALSE":
					nut=False
				else:
					# there are  nut dimensions provided in form  "nut_height nut_inner_dia nut_outer_dia top_face_dist_from_p2" 
					nut=attD['nut'].split()
			else:
				nut=False
				
			if 'washer_head' in attD.keys():
				if attD['washer_head'].upper() == "YES" or attD['washer_head'].upper() == "TRUE":
					washer_head=True
				elif attD['washer_head'].upper() == "NO" or attD['washer_head'].upper() == "FALSE":
					washer_head=False
				else:
					# there are  washer dimensions  provided in form  "washer_height washer_inner_dia washer_outer_dia top_face_dist_from_p2" 
					washer_head=attD['washer_head'].split()
			else:
				washer_head=False
				
			if 'washer_nut' in attD.keys():	
				if attD['washer_nut'].upper() == "YES" or attD['washer_nut'].upper() == "TRUE":
					washer_nut=True
				elif attD['washer_nut'].upper() == "NO" or attD['washer_nut'].upper() == "FALSE":
					washer_nut=False
				else:
					# there are  washer dimensions  provided in form  "washer_height washer_inner_dia washer_outer_dia top_face_dist_from_p2" 
					washer_nut=attD['washer_nut'].split()
			else:
				washer_nut=False
			
			if type=='rivet' or type=='countersunk':
				# get more attribs
				attrib_names=['bolt_head_diam','head_height']

				for curve in bolt_curves:
					GoFlag=True
					tmp_attributes=curve.getUserAttributes(attrib_names)
					attD={}
					# check if there is a dim in format 'm10' if not skip bolt
					# if type is not specified it will take default value 'hex' 
					# all other attributes will take default value False if not set
					for attrib in tmp_attributes:
						attD[attrib.getAttributeName()]=attrib.getStringValue()
				
				
				
				if 'bolt_head_diam' in attD.keys():
					bolt_head_diam=attD['bolt_head_diam']
				else:
					bolt_head_diam=False
					
				if 'head_height' in attD.keys():
					head_height=attD['head_height']
				else:
					head_height=False
			else:
				bolt_head_diam=0
				head_height=0
			
			verts= curve.getExteriorVertices()
			p2=[verts[0].getLocation().getX(), verts[0].getLocation().getY(), verts[0].getLocation().getZ()]
			p1=[verts[1].getLocation().getX(), verts[1].getLocation().getY(), verts[1].getLocation().getZ()]
			#print(p1)
			BoltD[curve.getPathName()]=[p1,p2,dim,type,nut,washer_head,washer_nut,bolt_head_diam,head_height]
			
			#create the bolt
		
	return 	BoltD
		

def SlaskScaleBoxPoints(box,scale_fact):
	IPhys1=box.getPoint1()
	IPhys2=box.getPoint2()
	
	# grow bounding box
	x1=IPhys1.getX()
	y1=IPhys1.getY()
	z1=IPhys1.getZ()

	x2=IPhys2.getX()
	y2=IPhys2.getY()
	z2=IPhys2.getZ()
	
	
	dx=(x2-x1)*scale_fact
	dy=(y2-y1)*scale_fact
	dz=(z2-z1)*scale_fact
	
	Point1=apex.Coordinate()
	Point2=apex.Coordinate()

	Point1.setX(x1-dx)
	Point1.setY(y1-dy)
	Point1.setZ(z1-dz)
	
	Point2.setX(x2+dx)
	Point2.setY(y2+dy)
	Point2.setZ(z2+dz)
	
	x1=Point1.getX()
	y1=Point1.getY()
	z1=Point1.getZ()

	x2=Point2.getX()
	y2=Point2.getY()
	z2=Point2.getZ()
	return Point1, Point2	
			
#
def old_ScaleBoxPoints(box,scale_fact):
	Point1=box.getPoint1()
	Point2=box.getPoint2()
	
	# grow bounding box
	x1=Point1.getX()
	y1=Point1.getY()
	z1=Point1.getZ()

	x2=Point2.getX()
	y2=Point2.getY()
	z2=Point2.getZ()
	
	dx=(x2-x1)*scale_fact
	dy=(y2-y1)*scale_fact
	dz=(z2-z1)*scale_fact

	Point1.setX(x1-dx)
	Point1.setY(y1-dy)
	Point1.setZ(z1-dz)
	
	Point2.setX(x2+dx)
	Point2.setY(y2+dy)
	Point2.setZ(z2+dz)
	
	x1=Point1.getX()
	y1=Point1.getY()
	z1=Point1.getZ()

	x2=Point2.getX()
	y2=Point2.getY()
	z2=Point2.getZ()
	return Point1, Point2
		
def TetmeshSolid(solid,size):
	_target = apex.entityCollection()
	_target.append(solid)
	result = apex.mesh.createSolidMesh(
		name = "",
		target = _target,
		meshSize = size,
		meshType = apex.mesh.SolidMeshElementShape.Tetra,
		elementOrder = apex.mesh.ElementOrder.Quadratic,
		refineMeshUsingCurvature = True,
		elementGeometryDeviationRatio = 0.10,
		elementMinEdgeLengthRatio = 0.20,
		coarsenMeshInternally = True,
		gradeFactor = 10.,
		ignoreBadEdges = True,
		collapseShortElementEdges = False,
		collapseElementEdgesShorterThan = 0.500000023749,
		createFeatureMeshOnFillets = False,
		createFeatureMeshOnChamfers = False,
		createFeatureMeshOnCylinders = False,
		createFeatureMeshOnSemiCylinders = False,
		createFeatureMeshOnWashers = False,
		createFeatureMeshOnQuadFaces = False,
		createLayeredMesh = False,
		thinSectionThreshold = 20.,
		numLayers = 2
	)		
				
def HexmeshSolid(solid,size):
	_target = apex.entityCollection()
	_target.append(solid)
	_SweepFace = apex.entityCollection()
	result = apex.mesh.createHexMesh(
		name = "",
		target = _target,
		meshSize = size,
		surfaceMeshMethod = apex.mesh.SurfaceMeshMethod.Pave,
		mappedMeshDominanceLevel = 2,
		elementOrder = apex.mesh.ElementOrder.Linear,
		refineMeshUsingCurvature = False,
		elementGeometryDeviationRatio = 0.10,
		elementMinEdgeLengthRatio = 0.20,
		sweepFace = _SweepFace,
		hexMeshMethod = apex.mesh.HexMeshMethod.Auto
	)
	return result
	
def CreatePartsFromSolids(model):
	numBodies       = 0
	numPartsCreated = 0
	numAssyCreated  = 0
	
	#print("Total starting Parts: "+str(parts.len()))
	for part in parts:
		if part.getName()!="ZoomBox":
			bodies = part.getGeometryBodies()
			numBodies += bodies.len()
			if bodies.len() > 1:
				try:
					assy = part.getParent()
					subAssy = assy.createAssembly(name = part.getName())
				except:
					subAssy = model_1.createAssembly(name = part.getName())
				numAssyCreated += 1
			for body in bodies:
				prt = subAssy.createPart(name = body.getName())
				body.setParent(prt)
				numPartsCreated += 1

	#print("Number of Bodies Found: "      +str(numBodies))
	#print("Number of Assemblies created: "+str(numAssyCreated))
	#print("Number of Parts created: "     +str(numPartsCreated))

def delete_glue_pairs():
	entities_5 = apex.entityCollection()
	entities_4 = apex.entityCollection()
	for i in range(100):
		try:
			meshindependenttie_4 = apex.attribute.getMeshIndependentTie( name = "/Model Files/Interactions::Interaction/Glue " + str(i) )
			entities_4.append(meshindependenttie_4)
			entities_5 += entities_4
		except:
			pass
	apex.deleteEntities(entities_5)



def getCommonParent(p1,p2,diam):

	model_1=apex.currentModel()
	new_part_full_name=model_1.getName() + "/" + "TMP"
	try:
		part_1 = apex.getPart( pathName = new_part_full_name )
		print("Part " + part_1.getName()  + " already exists")
	except:
		part_1 = model_1.createPart()
		updatedPartName_ = part_1.update( name = "TMP" )
		print("Created :" + "TMP" )

	#part_1 = apex.createPart()
	#updatedPartName_ = part_1.update( name = "AAA" )
	#	
	tmp_part_full_name=part_1.getPath() + "/" + part_1.getName()



	# start with curve- find solids around cylinder with diam.
	bolt_solid,bolt_surf=CreateCylinder(p1,p2,diam,part_1,model_1)
	
	_tmp=apex.EntityCollection()
	_tmp.append(bolt_surf)
		

	
	
	#print(bolt_solid.getName() )
	#edges_=get_edges_near_bolt(bolt_solid,grow_box_rate=1.0)
	_tmp.append(bolt_solid)
	
	# get bodies near bolt
	bolt_search_tol=1.0*diam
	near_bodies=get_bodies_near_bolt(bolt_solid,bolt_search_tol)
	

	
	geoms=apex.entityCollection()
	PartNameL=[]
	BodyOmitL=[]
	
	for body in near_bodies:
		parent_full_path=body.getPath()
		parent_body_full_path=body.getPathName()
		if parent_full_path not in PartNameL and parent_body_full_path not in BodyOmitL and parent_full_path != tmp_part_full_name:
			try:
				tmp_attributes=parent_body.getUserAttributes(['ParentPart'])
				parent_part_name=tmp_attributes[0].getStringValue()
				BodyOmitL.append(parent_body_full_path)
			except:
				# no attributes
				print(parent_body_full_path)
				PartNameL.append(parent_full_path)
				geoms.append(body)
		else:
			pass
		
		
	# old method	
	
	# for edge in edges_:
		# parent_body=edge.getBody()
		# parent_full_path=parent_body.getPath()
		# parent_body_full_path=parent_full_path + "/" + parent_body.getName()
		# if parent_full_path not in PartNameL and parent_body_full_path not in BodyOmitL and parent_full_path != tmp_part_full_name:
			# try:
				# tmp_attributes=parent_body.getUserAttributes(['ParentPart'])
				# parent_part_name=tmp_attributes[0].getStringValue()
				# BodyOmitL.append(parent_body_full_path)
			# except:
				# # no attributes
				# print(parent_body_full_path)
				# PartNameL.append(parent_full_path)
				# geoms.append(parent_body)
		# else:
			# pass
				

	# delete surf
	if len(_tmp) != 0:
		apex.deleteEntities(_tmp)
		# Get common parent
	
	AssyTreeD={}
	print("===")
	for part_name in PartNameL:
		tmp=part_name.split("/")
		tmp_path=""
		for item in tmp:
			tmp_path=tmp_path + "/" + item
			if tmp_path not in AssyTreeD.keys():
				AssyTreeD[tmp_path]=1
			else:
				AssyTreeD[tmp_path]+=1
	print("===")
		
	# find the longest key (=path)  with same no of hits as no of parts in partName List
	NoOfParts=len(PartNameL)
	test_path=""
	for key in AssyTreeD.keys():
		if AssyTreeD[key]==NoOfParts:
			if len(key) > len(test_path):
				test_path=key
	print("Common path: " + test_path )
	
	# test if path or Assy
	AssyFlag=False
	try:
		test_assy = apex.getAssembly( pathName = test_path[1:] )
		print(test_assy.getName())
		print("Assy: " + test_path[1:] + " found")
		AssyFlag=True
		return test_assy, AssyFlag
	except:
		AssyFlag=False
		print("Part: " + test_path[1:] + " found")
		test_part = apex.getPart( pathName = test_path[1:] )
		return test_part, AssyFlag


def exectuteOnExit(dict={}):
	apex.disableShowOutput()
	#apex.enableShowOutput()
	# clear highlights
	solids= getAllSolids()
	for solid in solids:
		solid.clearHighlight()
		cells=solid.getCells()
		for cell in cells:
			cell.clearHighlight()
		




def main(dict={}):

	## Start Connect to GUI code ####
	apex.setScriptUnitSystem(unitSystemName = r'''mm-kg-s-N-K''')
	_selected = apex.selection.getCurrentSelection()
	
	apex.enableShowOutput()
	
	
	myModel = apex.currentModel()
	
	#connName      = dict["name"]
	matlName      = dict["material"]
	fastenerType  = dict["fastenerType"]
	meshSize   = float(dict["meshsize"])
	
	boltTipGlueTolerance=float(dict["boltTipGlueTolerance"])
	boltTopTouchTolerance=float(dict["boltTopTouchTolerance"])
	boltFriction=float(dict["boltFriction"])
	
	preLoad=dict["preLoad"]
	
	# add method to pass curves specified by pathname through dict
	# with this method, preload and material name will come as well
	
	try:
		#print(dict)
		curve_part_str=dict["boltParts"]
		boltPartL=curve_part_str.split(',')
		boltPartName=boltPartL[0]
		matlName=boltPartL[1]
		preLoad=boltPartL[2]
		# get bolt Part - we know top Assy Name is "FEM"
		model_name=myModel.getName()
		boltPart = apex.getPart( pathName = model_name + "/FEM/" + boltPartName )
		dict_curves=boltPart.getCurves()

	except:
		print("No curves in dict")
		dict_curves=[]
	
	writeToCurvePart_Flag=False
	if dict["writeToCurveparent"].upper()=="TRUE":
		writeToCurvePart_Flag=True
	
	#distributionType  = dict["distributionType"]
	
	splitLocation=dict["splitLocation"]
	splitPercentage=float(dict["splitPercentage"])
	
	if splitPercentage > 100 or splitPercentage < 0:
		print("Error, Split percentage need to be between 0-100%")
		return
	
	splitDistance=float(dict["splitDistance"])
	
	
	
	#print("Name:           "+connName)
	print("Material:       "+matlName)
	print("Fastener type:  "+fastenerType)
	#print("Diameter:       "+str(fastenerDia))
	#print("Distribution Type:      "+ distributionType )
	
	
	
	if fastenerType == "":
		print("Fastener Type is BLANK!")
		return
	elif fastenerType=="Mentat (no split)":
		MentatFlag=True
	else:
		MentatFlag=False
	
	
	if meshSize <= 0.0:
		print("Invalid mesh size!")
		return
	
	if matlName == "None" :
		print("No material selected!")
		return
	
	# get the material that goes with the selected material name
	fastenerMatl = None
	matls = apex.catalog.getMaterials([{"path":myModel.getName()+"/Materials"}])
	for matl in matls:
		if matl.getName() == matlName:
			fastenerMatl = matl
			break
	if fastenerMatl == None:
		print("Unknown material?")
		return
	


	## End of Connect to GUI code ####


	# if there are no curves selected, process all visible curves
	if len(_selected) > 0:
		visible_curves=_selected
		
	elif len(dict_curves) > 0:
		visible_curves=dict_curves
	else:	
		# 1b: just process visible curves
		curves=getAllCurves()
		visible_curves=apex.entityCollection()
		for curve in curves:
			if curve.getVisibility() == 1:
				visible_curves.append(curve)
	


	BoltDict=ReadBoltCurves(visible_curves)
	#for key in BoltDict:
		#print(key)
		#print(BoltDict[key])
		#print("===")
	
	#MentatFlag=True
	BoltD=CreateBolt2(BoltDict,None,None,None,meshSize,matlName,MentatFlag,splitLocation,splitPercentage,splitDistance,boltTipGlueTolerance,boltTopTouchTolerance,boltFriction,preLoad,writeToCurvePart_Flag)

	# clear highlights
	#exectuteOnExit()

if __name__ == "__main__":
	pass