# coding: utf-8
#
# Copyright © 2020 MSC.Software Corporation
#  
# Permission to use Scripts
#  
# Permission is granted to MSC.Software customers with valid licenses of the MSC Apex™ software 
# to use script files posted on this page (collectively “Scripts”) solely for customers’ own authorized 
# internal use in conjunction with licensed use of the MSC Apex software.  
# No other use is permitted.  No license to MSC Apex or other software is granted under this Permission.
#  
# SCRIPTS ARE PROVIDED ON AN "AS-IS" BASIS, WITH NO WARRANTIES OF ANY KIND.  USE OF THE SCRIPTS IS AT CUSTOMER'S SOLE RISK.
#  
# IN NO EVENT SHALL MSC.SOFTWARE CORPORATION, ITS AFFILIATES, SUBSIDIARIES, SUPPLIERS, AGENTS, REPRESENTATIVES OR EMPLOYEES BE LIABLE FOR ANY 
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, TORT, 
# STRICT LIABILITY, OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION USE OF THE SCRIPTS, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.  
#  
# Customer shall indemnify, defend and hold MSC.Software Corporation, its affiliates, subsidiaries, suppliers, agents, 
# employees or representatives (collectively, “Indemnified Parties”) harmless from and against any third-party claims 
# brought against Indemnified Parties rising out of or in connection with Customer’s use of the Scripts.
#
# process: internal

import apex
import math
from get_stuff_3 import *

apex.setScriptUnitSystem(unitSystemName = r'''mm-kg-s-N-K''')



#### Start Rutman methods ###

from operator import itemgetter



def createSPCForTopNode(name, topNode, coordSys):
	_target = apex.entityCollection()
	_target.append(topNode)
	
	_constraintProp = apex.attribute.createDisplacementConstraintProperties(
       constrainTranslationX = False, 
       constrainTranslationY = False, 
       constrainTranslationZ = False, 
       constrainRotationX    = False, 
       constrainRotationY    = False, 
       constrainRotationZ    = True)

	_orientation = apex.construct.createOrientationByCoordinateSystem(coordSys)
	
	constraint = apex.environment.createDisplacementConstraint(
       name = name,
       constraintType = apex.attribute.ConstraintType.General, 
       applicationMethod = apex.attribute.ApplicationMethod.Direct, 
       target = _target, 
       constraintProperties = _constraintProp, 
       orientation = _orientation, 
       description = "Rutman Fastener Top Node for: "+name)





def crossProduct(v1, v2):
	a1 = v1.getX()
	a2 = v1.getY()
	a3 = v1.getZ()
	
	b1 = v2.getX()
	b2 = v2.getY()
	b3 = v2.getZ()
	
	s1 = a2*b3 - a3*b2
	s2 = a3*b1 - a1*b3
	s3 = a1*b2 - a2*b1
	
	v = apex.construct.Vector3D(x = s1, y = s2, z = s3)
	
	return v






def createCoord(name, nearNode, axisVec):
	x1Vec = makeUnitVector(axisVec)
	
	elms = nearNode.getElements()
	nodes = elms[0].getNodes()
	
	nodeIds = []
	for node in nodes:
		nodeIds.append(node.getId())
	nodeIds.append(nodeIds[0])
	nodes.append(nodes[0])
	adjNode = nodes[nodeIds.index(nearNode.getId())+1]
	
	y1Vec = createVector3D(nearNode, adjNode)
	y1Vec = makeUnitVector(y1Vec)
	
	z1Vec = crossProduct(x1Vec, y1Vec)
	z1Vec = makeUnitVector(z1Vec)
	
	d = z1Vec.getX()**2 + z1Vec.getY()**2
	if d == 0.0:
		_aa = 0.0
		if z1Vec.getZ() > 0.0:
			_bb = 0.0
		else:
			_bb = 0.0
		_gg = -1.0*math.atan2(x1Vec.getY(), x1Vec.getX())
	else:
		d = math.sqrt(d)
		
		xp = apex.construct.Vector3D(x=z1Vec.getY(), y=-1.*z1Vec.getX(), z=0.0)
		
		x1px = xp.getX()*x1Vec.getX() + xp.getY()*x1Vec.getY() + xp.getZ()*x1Vec.getZ()
		x1py = xp.getX()*y1Vec.getX() + xp.getY()*y1Vec.getY() + xp.getZ()*y1Vec.getZ()
		
		_aa = math.atan2(x1py, x1px)
		_bb = math.atan2(d, z1Vec.getZ())
		_gg = -1.0*math.atan2(xp.getY(), xp.getX())
		
	_aa = _aa*180./math.pi
	_bb = _bb*180./math.pi
	_gg = _gg*180./math.pi
	
	_aa = 1.0*_aa
	_bb = 1.0*_bb
	_gg = 1.0*_gg
	
	_alpha = _gg
	_beta  = _bb
	_gamma = _aa
	
	print("alpha: "+str(_alpha))
	print("beta:  "+str(_beta))
	print("gamma: "+str(_gamma))
	
	
	_orientation = apex.construct.createOrientation(alpha=_alpha, beta=_beta, gamma=_gamma)
	
	coordSys = apex.construct.createCoordinateSystemByEulerMethod(
            name = name,
            description = "",
            coordType = apex.construct.CoordinateSystemType.Cartesian,
            origin = nearNode,
            orientation=_orientation)
	
	return coordSys
	
	
	





def createCbush(name, plateNode, fastenerNode, thick, Eplate, NUplate, Efast, coord):

	# the cbush will reference the fastener coord frame which has x along the fastener
	# shaft.  this means that the cbush needs to reference y and z.

	Sy = Eplate*thick/(1.0-NUplate**2)
	Sz = Sy
	
	d = (1.0-NUplate**2)/Eplate + 1.0/Efast
	Ry = thick**3/12./d
	Rz = Ry

	_endPoint1 = apex.construct.createLocationByEntity(entity = plateNode)
	_endPoint2 = apex.construct.createLocationByEntity(entity = fastenerNode)
	
	_orientation = apex.construct.createOrientationByCoordinateSystem(coord)
	
	bushProps = apex.attribute.createConnectorProperties(
               translationalStiffnessX = 0.,
               translationalStiffnessY = Sy,
               translationalStiffnessZ = Sz,
               rotationalStiffnessX    = 0.,
               rotationalStiffnessY    = Ry,
               rotationalStiffnessZ    = Rz,
               linkMaterial = None)
	bushConnector = apex.attribute.createConnector(
               name = name+"_CBUSH",
               connectorType = apex.attribute.ConnectorType.Bushing,
               connectorProperties = bushProps,
               applicationMethod1 = apex.attribute.ApplicationMethod.Direct,
               applicationMethod2 = apex.attribute.ApplicationMethod.Direct,
               end1InterfacePoint = _endPoint1, 
               end2InterfacePoint = _endPoint2, 
               orientation = _orientation,
               description = "")
	return bushConnector





def createRbar(name, aNode, bNode):
	_endPoint1 = apex.construct.createLocationByEntity(entity = aNode)
	_endPoint2 = apex.construct.createLocationByEntity(entity = bNode)
	rbarProps = apex.attribute.createConnectorProperties(linkMaterial = None)
	rbarConnector = apex.attribute.createConnector(
               name = name+"_RBAR",
               connectorType = apex.attribute.ConnectorType.RigidLink,
               connectorProperties = rbarProps,
               applicationMethod1 = apex.attribute.ApplicationMethod.Direct,
               applicationMethod2 = apex.attribute.ApplicationMethod.Direct,
               end1InterfacePoint = _endPoint1, 
               end2InterfacePoint = _endPoint2, 
               description = "")
	return rbarConnector





def createFlexConnector(name, fastenerDia, fastenerMatl, n0, n1):
	_endPoint1 = apex.construct.createLocationByEntity(entity = n0)
	_endPoint2 = apex.construct.createLocationByEntity(entity = n1)

	flexConnectorProps = apex.attribute.createConnectorProperties(
              diameter     = fastenerDia,
              linkMaterial = fastenerMatl)
	flexConnector = apex.attribute.createConnector(
               name = name+"_BAR",
               connectorType = apex.attribute.ConnectorType.FlexibleLink,
               connectorProperties = flexConnectorProps,
               applicationMethod1 = apex.attribute.ApplicationMethod.Direct,
               applicationMethod2 = apex.attribute.ApplicationMethod.Direct,
               end1InterfacePoint = _endPoint1, 
               end2InterfacePoint = _endPoint2, 
               description = "")
	return flexConnector






def myDot(aVec, bVec):
	dot  = aVec.getX()*bVec.getX()
	dot += aVec.getY()*bVec.getY()
	dot += aVec.getZ()*bVec.getZ()
	
	cosTheta = dot/aVec.getLength()/bVec.getLength()
	
	return cosTheta






def dotProduct(aVec, bVec):
	dot  = aVec.getX()*bVec.getX()
	dot += aVec.getY()*bVec.getY()
	dot += aVec.getZ()*bVec.getZ()

	return dot






def createVector3D(p1, p2):
	dx = p2.getX() - p1.getX()
	dy = p2.getY() - p1.getY()
	dz = p2.getZ() - p1.getZ()

	vec = apex.construct.Vector3D(dx, dy, dz)
	
	return vec





def makeUnitVector(vec):
	x = vec.getX()/vec.getLength()
	y = vec.getY()/vec.getLength()
	z = vec.getZ()/vec.getLength()
	
	v = apex.construct.Vector3D(x, y, z)
	
	return v





def getNormalAtNode(nn):
	elms = nn.getElements()
	# for the moment, i am just going to get the normal of the first element
	norm = elms[0].getNormal()
	
	return norm



def createRutman(nodes_list,matlName,fastenerDia,name=""):
	
	myModel = apex.currentModel()
	
	# get all nodes in the model
	allParts = myModel.getParts(True)
	
	if matlName == "None":
		print("No material selected!")
		return
	
	# get the material that goes with the selected material name
	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
	
	
	
	
	if 1==0:
		_selected = apex.selection.getCurrentSelection()
		
		myModel = apex.currentModel()
		
		matlName    = dict["material"]
		fastenerDia = float(dict["diameter"])
		tol         = float(dict["tolerance"])
		gripLength  = float(dict["gripLength"])
		
		print("Diameter:       "+str(fastenerDia))
		print("Grip Length:    "+str(gripLength))
		print("Material:       "+matlName)
		print("Tolerance:      "+str(tol))
		

		
		if fastenerDia <= 0.0:
			print("Invalid fastener diameter!")
			return
		
		if tol <= 0.0:
			print("Invalid tolerance!")
			return
		
		if gripLength <= 0.0:
			print("Invalid grip length!")
			return
		
		
		# get all nodes in the model
		allParts = myModel.getParts(True)
		
		nodes = apex.entityCollection()
		meshedParts = apex.entityCollection()
		for part in allParts:
			meshes = part.getMeshes()
			if meshes.len() == 0: continue
			meshedParts.append(part)
			for mesh in meshes:
				nodes += mesh.getNodes()
		
		# if there are no meshes, there is no need to create fasteners
		if meshedParts.len() == 0:
			print("No meshed surfaces in any Part!")
			return
		# if there are no nodes, there is no need to create fasteners
		if nodes.len() == 0:
			print("No nodes in the model?")
			return
		
		# the selected points represent the fastener locations.  for now, i am only allowing points to be selected
		selPoints = apex.entityCollection()
		selPoints = [sel for sel in _selected]
		
		mySearch = apex.utility.ProximitySearch()
		#nip = apex.IPhysicalCollection()
		nip = [node for node in nodes]
		#for node in nodes:
		#	nip.append(node)
		mySearch.insertList(nip)
		
		# + I can only use nodes once.  i cannot re-use fastener nodes for other fasteners.
		# + This of course only works within the script.  i am not currently screening for pre-created fasteners
		usedNodes   = []
		usedNodeIds = []
		
		# + Each selected point is assumed to represent a fastener location.  The fastener will be located based
		# on the closest node to the point.  Currently there are no restrictions on the "closest" node.  In the future
		# I may limit it based on Part or similar
		
		for point in selPoints:
			connData = [] # data associated with this fastener
			name = "RF-"+point.getName()
			# Nearest point may be tricky.  In the PCL that I wrote for rutman fasteners, I also had a search direction.  Here I do not.
			# Hopefully, I do not find and create fasteners within the plane of the plate.
			nearestObjects = mySearch.findNearestObjects(point, 6) # I am doing 6 here in case some nodes are already used.  the number 6 is not magical.
			foundObjects = nearestObjects.foundObjects()
			dists        = nearestObjects.getDistances()
			foundNearNode = False
			for item in enumerate(foundObjects):
				count    = item[0]
				nearNode = item[1]
				dist     = dists[count]
				# If the point is on the plate or perhaps a seed point, then a small tolerance is likely good here.  A larger tolerance
				# would be appropriate if the point is perhaps between the plates.  For now, I am using the user defined tolerance
				if dist > tol: continue
				if nearNode.getId() in usedNodeIds: continue
				connectedElms = nearNode.getElements()
				if connectedElms.len() == 0: continue
				connectedElmIds = [elm.getId() for elm in connectedElms]
				foundNearNode = True
				break
			print("Near node identified: "+str(foundNearNode))
			print("Near node ID: "+str(nearNode.getId()))
			print("Near node distance: "+str(dists[count]))
			if foundNearNode == False: continue
			
			# At this point, I have a node that is within tolerance of the start location
			
			# Now find nodes close to my starting node
			nearestObjects = mySearch.findNearestObjects(nearNode, 30) # nothing magical about 30, this may need to be increased
			foundObjects = nearestObjects.foundObjects()
			dists        = nearestObjects.getDistances()
			vectors      = nearestObjects.getVectors()
			
			# Search for the closest node to the node just found.  This will be used to define the orientation
			# of the fastener
			
			
			foundNearNode = False
			for item in enumerate(foundObjects):
				count = item[0] # use this to locate this item in dists and vectors
				obj   = item[1] # this is the current near node for consideration
				
				if obj.getId() == nearNode.getId(): continue # cannot find or reuse the start node
				if obj.getId() in usedNodeIds:      continue # cannot reuse a node that has already been found
				elms = obj.getElements()
				if len(elms) == 0: continue  # must connect to a node that connects to an element
				foundConnectedElm = False
				for elm in elms:
					if elm.getId() in connectedElmIds: # cannot connect to a node with shared element, can only use an element 1 time
						foundConnectedElm = True
						break
				if foundConnectedElm: continue
				if dists[count] <= gripLength: # we have found a candidate node
					
					x = -1*vectors[count].getX()/vectors[count].getLength()
					y = -1*vectors[count].getY()/vectors[count].getLength()
					z = -1*vectors[count].getZ()/vectors[count].getLength()
					axisVec = apex.construct.Vector3D(x, y, z)
					foundNodes   = [nearNode, obj] # nodes in this fastener
					foundNodeIds = [nearNode.getId(), obj.getId()]
					usedNodes.append(nearNode) # nodes in any fastener
					usedNodes.append(obj)
					usedNodeIds.append(nearNode.getId())
					usedNodeIds.append(obj.getId())
					for elm in elms:
						connectedElms.append(elm)
						connectedElmIds.append(elm.getId())
					foundNearNode = True
					print("Found node near node: "+obj.getId())
					print("Distance: "+str(dists[count]))
					print("Axis Vector")
					print(axisVec.getX())
					print(axisVec.getY())
					print(axisVec.getZ())
					break
			if foundNearNode == False:
				print("Node near node not found")
				continue
			
			# Now i will continue to iterate through the found objects.
			# This may be a good idea or it may not.  Testing will tell.
			# The 30 number may need to be increased as I expect to find
			# a lot of nodes in the mesh body for the start node as well as in other bodies
			for item in enumerate(foundObjects):
				k   = item[0] # use this to locate this item in dists and vectors
				obj = item[1] # this is the current near node for consideration
				if obj.getId() in foundNodeIds:
					#print("Node already exists in the found ids")
					continue # Cannot reuse a node in this fastener
				if obj.getId() in usedNodeIds:
					#print("Node already exists in the used ids")
					continue # Cannot reuse a node in any fastener
				elms = obj.getElements()
				if elms.len() == 0:
					#print("No elements connect to the node")
					continue  # must connect to a node that connects to an element
				foundConnectedElm = False
				for elm in elms:
					if elm.getId() in connectedElmIds:
						foundConnectedElm = True
						break
				if foundConnectedElm:
					#print("Node already connects to this element")
					continue # must connect only once to an element.  this may be a bit restrictive for a coarse mesh.
				
				if dists[k] > gripLength: continue # Node not within the grip length
				
				cosTheta = myDot(axisVec, vectors[k])
				
				#print("Vector check for node: "+str(obj.getId()))
				#print(cosTheta)
				#print(vectors[k].getX())
				#print(vectors[k].getY())
				#print(vectors[k].getZ())
				
				if abs(abs(cosTheta) - 1.0) > .0002: # vectors not aligned.  this is about 1.15deg
					#print("Vectors are not aligned")
					continue
				
				# If I am here the vectors are aligned
				foundNodes.append(obj)	# this node is part of the fastener
				foundNodeIds.append(obj.getId())
				usedNodes.append(obj)
				usedNodeIds.append(obj.getId())
				
				# ?should i also add to used Nodes?
				
				break
				
			# Now I need to sort the found nodes in ascending order based on the axisVector
			cData = [[nearNode, 0.0]]
			for node in foundNodes:
				if node.getId() == nearNode.getId(): continue # near node has already been added to cData
				
				vec = createVector3D(nearNode, node) # create a vector from the nearNode to the current node
				dot = dotProduct(vec, axisVec)       # dot should be (hopefully) the location of the node along the axis
				
				cData.append([node, dot])
			sortedData = sorted(cData, key=itemgetter(1), reverse=False) # ascending order sort on dot
			
	#connData = sortedData
	connData=[]
	for node in nodes_list:
		connData.append([node,100.])
	# At this point connData is simply
	# 0 = node connected to plate elm
	# 1 = distance
	
	# create coordinate frame
	
	#first and second node give the axis
	nodeA=nodes_list[0]
	nodeB=nodes_list[1]
	pntA=[nodeA.getX(),nodeA.getY(),nodeA.getZ()]
	pntB=[nodeB.getX(),nodeB.getY(),nodeB.getZ()]
	
	axisVec = apex.construct.Vector3D(pntB[0]-pntA[0], pntB[1]-pntA[1], pntB[2]-pntA[2])
	coord = createCoord(name, nodeA, axisVec)
	
	
	
	# CREATE NODES FOR THE FASTENERS (EXCEPT THE TOP AND BOTTOM, THOSE ARE CREATED LATER)
	for data in connData:
		node = data[0]
		elms = node.getElements()
		# for now just get data for first element
		section = elms[0].getSection()
		thick   = section.getThickness()
		matl    = elms[0].getMaterial()
		E       = matl.getElasticModulus()
		NU      = matl.getPoissonRatio()
		
		nn = apex.mesh.createNodeByPickLocation(target=node, location=node)
		
		data.append(nn)
		data.append(thick)
		data.append(E)
		data.append(NU)
		
		#foundNodes.append(nn)
		#foundNodeIds.append(nn.getId())
		#usedNodes.append(nn)
		#usedNodeIds.append(nn.getId())

	#print("length of connData: "+str(len(connData)))
	#connData
	# 0 = node connected to plate elm
	# 1 = distance
	# 2 = new node created to connect bar
	# 3 = thickness of plate
	# 4 = E
	# 5 = NU
	
	# CREATE FASTENER BAR ELEMENTS AND PLATE RBAR ELEMENTS
	for k in range(len(connData)-1):
		# create flexible connector
		nPlate0 = connData[k][0]   # node for 1st plate
		nPlate1 = connData[k+1][0] # node for 2nd plate
		n0     = connData[k][2]    # new node for 1st plate
		n1     = connData[k+1][2]  # new node for 2nd plate
		
		createFlexConnector(name, fastenerDia, fastenerMatl, n0, n1)
		
		createRbar(name, nPlate0, nPlate1)
	
	# CREATE CBUSH CONNECTING PLATES TO THE FASTENERS
	Efast = fastenerMatl.getElasticModulus()
	for data in connData:
		nPlate = data[0] # node connected to plate element
		nn     = data[2] # new node connected to bar element
		
		createCbush(name, nPlate, nn, data[3], data[4], data[5], Efast, coord)
	
	
	# CREATE TOP BAR AND RBAR ELEMENT
	nTop  = connData[0][0]   # node for top plate
	thick = connData[0][3]   # thickness of top plate
	# need to know the part this top node is in
	# very inefficient
	foundPart = False
	for part in allParts:
		meshes = part.getMeshes()
		for mesh in meshes:
			meshNodes = mesh.getNodes()
			if nTop in meshNodes:
				myPart = part
				foundPart = True
				break
		if foundPart == True: break
	
	tmpPart = myPart.getParent().createPart("tmpPart")
	# do i need to check if I don't find a part?
	x = nTop.getX() - .5*thick*axisVec.getX()
	y = nTop.getY() - .5*thick*axisVec.getY()
	z = nTop.getZ() - .5*thick*axisVec.getZ()
	pt = apex.geometry.createPointXYZ( x = x, y = y, z = z )
	vv = pt.getVertices()
	nn = apex.mesh.createNodeByPickLocation(target=vv[0], location=apex.Coordinate(pt.getX(), pt.getY(), pt.getZ()))
	_target = tmpPart.getMeshes()
	
	createFlexConnector(name, fastenerDia, fastenerMatl, nn, nTop)
	createRbar(name, nn, nTop)
	
	createSPCForTopNode(name, nn, coord)
	
	apex.reparent(target = _target, parent = myPart)
	_target = apex.entityCollection()
	_target.append(tmpPart)
	apex.deleteEntities(target=_target)
	
	# CREATE BOTTOM BAR AND RBAR ELEMENT
	k = len(connData)-1
	nBot  = connData[k][0]   # node for bottom plate
	thick = connData[k][3]   # thickness of bottom plate
	# need to know the part this bottom node is in
	# very inefficient
	foundPart = False
	for part in allParts:
		meshes = part.getMeshes()
		for mesh in meshes:
			meshNodes = mesh.getNodes()
			if nTop in meshNodes:
				myPart = part
				foundPart = True
				break
		if foundPart == True: break
	
	tmpPart = myPart.getParent().createPart("tmpPart")
	# do i need to check if I don't find a part?
	x = nBot.getX() + .5*thick*axisVec.getX()
	y = nBot.getY() + .5*thick*axisVec.getY()
	z = nBot.getZ() + .5*thick*axisVec.getZ()
	pt = apex.geometry.createPointXYZ( x = x, y = y, z = z )
	vv = pt.getVertices()
	nn = apex.mesh.createNodeByPickLocation(target=vv[0], location=apex.Coordinate(pt.getX(), pt.getY(), pt.getZ()))
	_target = tmpPart.getMeshes()
	
	createFlexConnector(name, fastenerDia, fastenerMatl, nBot, nn)
	createRbar(name, nBot, nn)
	
	apex.reparent(target = _target, parent = myPart)
	_target = apex.entityCollection()
	_target.append(tmpPart)
	apex.deleteEntities(target=_target)





#### End Rutman methods ###







def highlight_stuff(item,r,g,b):
	col1=apex.ColorRGB(r,g,b)					
	#if surf.getVisibility() == 1:		
	item.highlight(col1,2,3)

def calcDistance(n1, n2):
	loc1 = n1.asNode().getLocation()
	loc2 = n2.asNode().getLocation()
	dx = loc2.getX() - loc1.getX()
	dy = loc2.getY() - loc1.getY()
	dz = loc2.getZ() - loc1.getZ()
	
	dist = dx**2 + dy**2 + dz**2
	
	if dist > 0.0: dist = math.sqrt(dist)
	
	return dist

def getNearestNodeInPart(point):
	#import apex
	part = point.getParent()
	
	meshes = part.getMeshes()
	nodes = apex.entityCollection()
	for mesh in meshes:
		nodes += mesh.getNodes()
	
	iNodes = apex.IPhysicalCollection()
	for node in nodes:
		iNodes.append(node.asEntity())
		
	mySearch = apex.utility.ProximitySearch()
	mySearch.insertCollection(iNodes)
	
	loc = point.getLocation()
	nearestObject = mySearch.findNearestObject(loc)
	nearNode = nearestObject.getNearestObject()
	
	return nearNode

def getFastenerMaterial():
	matlName = "fastenerMatl"
	matlE    = 10.0E6
	matlNU   = 0.3
	matlRHO  = 0.000261598  #slinches

	allMatls = apex.catalog.getMaterials([{"path":apex.currentModel().getPath()}])
	matlExists = False
	for matl in allMatls:
		if matl.getName() == matlName:
			matlExists = True
			myMatl = matl
			break

	if not matlExists:
		myMatl = apex.catalog.createMaterial( name = matlName, description = "", color = [ 64, 254, 250 ] )
		myMatl.update(elasticModulus = matlE, poissonRatio = matlNU, density = matlRHO)
	
	return myMatl


def v_lgth(cord2):
	lgth = ((cord2[0]) ** 2 + (cord2[1]) ** 2 + (cord2[2]) ** 2) ** 0.5
	return lgth
	
def getCurveFaceIntersect(curve,faces):
	_target = apex.EntityCollection()
	_target.extend( curve.getEdges() )

	
	_splitter = apex.EntityCollection()
	_splitter.extend( faces )
	
	result = apex.geometry.createPointCurveSurface(
		targetCurves = _target,
		intersectingSurfaces = _splitter
	)

	return result
	
def createCurvesFromBox(p1,p2, p3, p4):
	out_curves=apex.EntityCollection()

	_target = [
		apex.Coordinate( p1[0], p1[1], p1[2] ),
		apex.Coordinate( p2[0], p2[1], p2[2] ),
		apex.Coordinate( p3[0], p3[1], p3[2] ),
		apex.Coordinate( p4[0], p4[1], p4[2] ),
		apex.Coordinate( p1[0], p1[1], p1[2] )
	]
	_iphysicalCollection = apex.IPhysicalCollection() 
	for point in _target :
		_iphysicalCollection.append(point)
	result = apex.geometry.createCurve(
		target = _iphysicalCollection,
		behavior = apex.geometry.CurveBehavior.Polyline
	)
	out_curves.append(result)			
			
	return out_curves
		
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 createCurvesFromPts(p1,p2):

	_target = [
		apex.Coordinate( p1[0], p1[1], p1[2] ),
		apex.Coordinate( p2[0], p2[1], p2[2] ),
	]
	_iphysicalCollection = apex.IPhysicalCollection() 
	for point in _target :
		_iphysicalCollection.append(point)
	result = apex.geometry.createCurve(
		target = _iphysicalCollection,
		behavior = apex.geometry.CurveBehavior.Polyline
	)

	return result

def create_guides_from_curves(faces,curves):

	result = apex.geometry.createMeshControlEdges(
		target = faces,
		guides = curves,
		)

def createRigidConnector(node1,node2, connector_name):
	_endPoint1 = apex.construct.createLocationByEntity(entity = node1)	
	_endPoint2 = apex.construct.createLocationByEntity(entity = node2)

	connectorproperties_1 = apex.attribute.createConnectorProperties( linkMaterial = None)
	connector_1 = apex.attribute.createConnector(
		name = connector_name,
		connectorType = apex.attribute.ConnectorType.RigidLink,
		connectorProperties = connectorproperties_1,
		applicationMethod1 = apex.attribute.ApplicationMethod.Direct,
		applicationMethod2 = apex.attribute.ApplicationMethod.Direct,
		end1InterfacePoint = _endPoint1, 
		end2InterfacePoint = _endPoint2, 
		description = ""   #defaults to " "
	)	
	return 	connector_1
	
def createDiscreteTieConnector(node1,node2,connector_name):
	
	_attachmentRegion = apex.EntityCollection()
	_attachmentRegion.append(node1 )
	
	connector_name="RBE2_xyz"

	_referenceRegion = node2
	nodetie_3 = apex.attribute.createNodeTie(
		name = connector_name,
		distributionType  = apex.attribute.DistributionType.Rigid,
		distributionMode  = apex.attribute.DistributionMode.Custom,
		dofDefinitionMode  = apex.attribute.DOFDefinitionMode.All,
		referencePoint  = _referenceRegion,
		attachmentRegions  = _attachmentRegion,
		dependentDoF = "123",
		description = ""   #defaults to " "
	)	
	
def createFlexibleLinkConnection(node1,node2,MaterialName,Diameter=10,connector_name=""):
	_endPoint1 = apex.construct.createLocationByEntity(entity = node1)	
	_endPoint2 = apex.construct.createLocationByEntity(entity = node2)
	
	if connector_name=="":
		connector_name="FlexibleLink_Dia" + ("%2.1f" % (Diameter) )
	
	# Create material unless existing
	try:
		_material = apex.catalog.getMaterial(name = MaterialName)
	except:
		# create Bolt Material
		_material = apex.catalog.createMaterial( name = "Material 1", description = "", color = [ 64, 254, 250 ] )
		_material.update(name = MaterialName)
		_material.update(elasticModulus = 205000.)
		_material.update(poissonRatio = 0.29)
		_material.update(density = 0.00000785)
	
	# Create Flexible link properties
	connectorproperties_1 = apex.attribute.createConnectorProperties(
		stiffness = 0.0,
		damping = 0.0,
		translationalStiffnessX = 0.0,
		translationalStiffnessY = 0.0,
		translationalStiffnessZ = 0.0,
		rotationalStiffnessX = 0.0,
		rotationalStiffnessY = 0.0,
		rotationalStiffnessZ = 0.0,
		translationalDampingX = 0.0,
		translationalDampingY = 0.0,
		translationalDampingZ = 0.0,
		rotationalDampingX = 0.0,
		rotationalDampingY = 0.0,
		rotationalDampingZ = 0.0,
		diameter = Diameter,
		linkMaterial = _material
	)
	
	
	# create the connector	
	connector_1 = apex.attribute.createConnector(
		name = connector_name,
		connectorType = apex.attribute.ConnectorType.FlexibleLink,
		connectorProperties = connectorproperties_1,
		applicationMethod1 = apex.attribute.ApplicationMethod.Direct,
		applicationMethod2 = apex.attribute.ApplicationMethod.Direct,
		end1InterfacePoint = _endPoint1, 
		end2InterfacePoint = _endPoint2, 
		description = ""   #defaults to " "
	)

def getConnectedPlateInfo(node):
	elms = node.getElements()
	# for now just get data for first element
	section = elms[0].getSection()
	thick   = section.getThickness()
	matl    = elms[0].getMaterial()
	E       = matl.getElasticModulus()
	NU      = matl.getPoissonRatio()
	
	return thick,E,NU

def get3dPointDist(Pt1,Pt2):
	p1=[Pt1.getX(), Pt1.getY(), Pt1.getZ()]
	p2=[Pt2.getX(), Pt2.getY(), Pt2.getZ()]
	return getDist(p1,p2)
	
def getDist(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 createHuthConnection(node1,node2,MaterialName,Diameter,a,b,n,connector_name=""):

	# Outer plates are organized so that n=1 (single shear)
	# inner plates are double shear, that is n=2
	
	# Outer plates always represent index(2) entitites in formula below for double shear plates
	
	connector_name="Huth_a" + "%4.2f" % (a) + "_b" + "%4.2f" % (b) + "_n" + "%d" % (n)


	# Create fastener material unless existing
	try:
		_material = apex.catalog.getMaterial(name = MaterialName)
	except:
		# create Bolt Material
		_material = apex.catalog.createMaterial( name = "Material 1", description = "", color = [ 64, 254, 250 ] )
		_material.update(name = MaterialName)
		_material.update(elasticModulus = 205000.)
		_material.update(poissonRatio = 0.29)
		_material.update(density = 0.00000785)



	_endPoint1 = apex.construct.createLocationByEntity(entity = node1)	
	_endPoint2 = apex.construct.createLocationByEntity(entity = node2)
	
	#plates
	t1,E1,NU1=getConnectedPlateInfo(node1)
	t2,E2,NU2=getConnectedPlateInfo(node1)
	#fastener
	Ef=_material.getElasticModulus()
	
	L=get3dPointDist(node1,node2)
	S=(3.14159*Diameter**2)/4
	
	# Huth formula

	
    #Axial Stiffness 
	
	#K1=Ef*S/L
    # E3=Fastener’s Young module, S=fastener section area, L= Fastener’s length.
	K1=Ef*S/L
	
	#Shear Stiffness
	#K2=K3=1/C
	
	#C=( (t1 + t2) / (2 * d))**a  *(b/n) * ( 1/(t1 * E1)  + b2 /(n * t2 * E2)   +  1 /(2 * t1 * Ef) + 1 /(2 * n * t2 * Ef)  )
	
	# 
	# n=1 for single shear and n=2 for double shear, b is equal to 3 for bolted metallic, 2.2 for riveted metallic, and 4.2 for bolted graphite/epoxy.
	#      Type                 a       b
	#Bolted metallic           2/3      3.0
	#Riveted metallic          2/5      2.2
	#Bolted graphite/epoxy     2/3      4.2
	
	#Rotational Stiffness
	K4=100
	
	# Bending Stiffneess
	
	#K5,K6=1/eps* ( (max(K1,K2) *L**2/4 )
	# eps=100

	d=Diameter
	
	C=( (t1 + t2) / (2 * d))**a  *(b/n) * ( 1/(t1 * E1)  + 1 /(n * t2 * E2)   +  1 /(2 * t1 * Ef) + 1 /(2 * n * t2 * Ef)  )
	
	K2=K3=1/C
	
	eps=100
	K5=K6= 1/eps * ( (max(K2,K3) *L**2 / 4 ) )
	
	print("Calculated Huth stiffnesses:")
	print("K1 "+"%12.3f" % (K1) )
	print("K2 "+"%12.3f" % (K2))
	print("K3 "+"%12.3f" % (K3))
	print("K4 "+"%12.3f" % (K4))
	print("K5 "+"%12.3f" % (K5))
	print("K6 "+"%12.3f" % (K6))
	
	
	connectorproperties_1 = apex.attribute.createConnectorProperties(
		translationalStiffnessX = K1,
		translationalStiffnessY = K2,
		translationalStiffnessZ = K3,
		rotationalStiffnessX = K4,
		rotationalStiffnessY = K5,
		rotationalStiffnessZ = K6,
		linkMaterial = None
	)
	connector_1 = apex.attribute.createConnector(
		name = connector_name,
		connectorType = apex.attribute.ConnectorType.Bushing,
		connectorProperties = connectorproperties_1,
		applicationMethod1 = apex.attribute.ApplicationMethod.Direct,
		applicationMethod2 = apex.attribute.ApplicationMethod.Direct,
		end1InterfacePoint = _endPoint1, 
		end2InterfacePoint = _endPoint2, 
		description = ""   #defaults to " "
	)
	
	
		
def create_Multi_Sheet_Connector(nodes_list,ConnectionType,Diameter,a=0,b=0,MatName="Bolt_Mat",connector_name=""):

			
	print("Creating Connectors")
	
	if ConnectionType == "Rutman":
		createRutman(nodes_list,MatName,Diameter,"RF_" + connector_name)
		return
	
	connector_name= ConnectionType
	
	
	# N layered connection N>2
	if len(nodes_list) > 2:
		n=0
		N=len(nodes_list) -1
		print("There are " + str(N+1) + " layers in list")

		while n < N-1:
			if ConnectionType=="RigidLink":
				createRigidConnector(nodes_list[n], nodes_list[n+1], connector_name)
			elif ConnectionType=="DiscreteTie123":
				createDiscreteTieConnector(nodes_list[n], nodes_list[n+1], connector_name)
			elif ConnectionType=="FlexibleLink":
				createFlexibleLinkConnection(nodes_list[n], nodes_list[n+1],MatName,Diameter=10,connector_name="")
			elif ConnectionType=="Huth":
				if n==0:
					# outer layer is single shear (n_value=1), and outer node ( nodes_list[0] ), need to have index 2
					createHuthConnection(nodes_list[n+1], nodes_list[n],MatName,Diameter,a,b,1,connector_name="")
				else:
					createHuthConnection(nodes_list[n], nodes_list[n+1],MatName,Diameter,a,b,2,connector_name="")	
			else:
				pass
			n+=1
			
		## Final connector n=N-1
		if ConnectionType=="RigidLink":
			createRigidConnector(nodes_list[N-1],nodes_list[N], connector_name)
		elif ConnectionType=="DiscreteTie123":
			createDiscreteTieConnector(nodes_list[N-1],nodes_list[N], connector_name)
		elif ConnectionType=="FlexibleLink":
				createFlexibleLinkConnection(nodes_list[N-1],nodes_list[N],MatName,Diameter=10,connector_name="")
		elif ConnectionType=="Huth":
			#outer layer is single shear (n_value=1), and outer node ( nodes_list[N] ), need to have index 2)
			createHuthConnection(nodes_list[N-1],nodes_list[N],MatName,Diameter,a,b,1,connector_name="")		
		else:
			pass
	
	# Two layered connection
	
	elif len(nodes_list) == 2 :
		if ConnectionType=="RigidLink":
			createRigidConnector(nodes_list[0],nodes_list[1], connector_name)
		elif ConnectionType=="DiscreteTie123":
			createDiscreteTieConnector(nodes_list[0],nodes_list[1], connector_name)
		elif ConnectionType=="FlexibleLink":
			createFlexibleLinkConnection(nodes_list[0],nodes_list[1],MatName,Diameter=10,connector_name="")	
		elif ConnectionType=="Huth":
			# single shear n_value=1
			createHuthConnection(nodes_list[0],nodes_list[1],MatName,Diameter,a,b,1,connector_name="")	
		else:
			pass
		
def getNearestNodefromFaces(point,faces):
	#import apex
	part = point.getParent()
	#meshes=apex.MeshBodyCollection()
	nodes = apex.entityCollection()
	
	for face in faces:
		tmp_body=face.getBody()
		nodes.extend(tmp_body.getNodes())
		#tmp_meshes=tmp_body.getMeshes()
		#meshes.extend(tmp_meshes)
	
		
		
	#meshes = part.getMeshes()
	#nodes = apex.entityCollection()
	#for mesh in meshes:
	#	nodes += mesh.getNodes()
	
	iNodes = apex.IPhysicalCollection()
	for node in nodes:
		iNodes.append(node.asEntity())
		
	mySearch = apex.utility.ProximitySearch()
	mySearch.insertCollection(iNodes)
	
	loc = point.getLocation()
	nearestObject = mySearch.findNearestObject(loc)
	nearNode = nearestObject.getNearestObject()
	
	return nearNode

	
	
def CreateSeedPointFromLocation(point_1,faces):
	_sources = apex.EntityCollection()
	_sources.extend( point_1.getVertices() )

	_targets = apex.EntityCollection()
	_targets.extend(faces)
	
	result = apex.mesh.createSeedPointsBySourceTarget(sources = _sources, targets = _targets, searchDistance = 5.0, cleanupTolerance = 1.0)
	
	#node=getNearestNodefromFaces(point_1,faces)
	
	return
	

def main(dict={}):

	import apex
	from apex.construct import Point3D, Point2D

	# our stuff
	myModel = apex.currentModel()
		
	fastenerType = dict["fastenerType"]
	Diameter  = float(dict["diameter"])
	matlName      = dict["material"]
	HuthType=dict["HuthType"]	

	
	#print("Name:           "+connName)
	print("Material:       "+matlName)
	print("Fastener type:  "+fastenerType)
	print("Diameter:       "+str(Diameter))
	#print("Distribution Type:      "+ distributionType )
	
	if fastenerType == "":
		print("Fastener Type is BLANK!")
		return
	
	if Diameter <= 0.0:
		print("Invalid fastener diameter!")
		return
	
	if matlName == "None" and  fastenerType != "Rigid Link":
		print("No material selected!")
		return
	if fastenerType != "Rigid Link" :	
		# 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
	
	_selected = apex.selection.getCurrentSelection()
	#_selected = getAllCurves()
	
	
	

	if fastenerType=="Rigid Link":
		ConnectorType="RigidLink"
	elif fastenerType=="Discrete Tie RBE2-dof 123":
		ConnectorType="DiscreteTie123"
	elif fastenerType=="Flexible Link":
		ConnectorType="FlexibleLink"
	elif fastenerType=="Huth-Connector":
		ConnectorType="Huth"
	elif fastenerType=="Rutman":
		ConnectorType="Rutman"	
	else:
		print("Something is wrong ",fastenerType)
	
	
	#Huth types["Bolted metallic", "Riveted metallic", "Bolted graphite/epoxy"]
	#      Type                 a       b
	#Bolted metallic           2/3      3.0
	#Riveted metallic          2/5      2.2
	#Bolted graphite/epoxy     2/3      4.2
	a=b=0
	
	if HuthType=="Bolted metallic":
		a=2/3
		b=3.0
	elif HuthType=="Riveted metallic":
		a=2/5
		b=2.2
	elif HuthType=="Bolted graphite/epoxy":
		a=2/3
		b=4.2
	else:
		print("Something is wrong(2) ")
	
	#_selected=getAllCurves()
	curves=_selected

	# check if part exists:
	part_exist_flag=False
	for part in myModel.getParts():
		if part.getName().find("ConnectorPoints") > -1:
			print("part exists")
			part_exist_flag=True
			break
	if not part_exist_flag:
		part_1 = apex.createPart()
		updatedPartName_ = part_1.update( name = "ConnectorPoints" )
	
		
	#curves=getAllCurves()
	faces=getAllSurfFaces()

	print("There are " + str(len(curves)) + "  curves selected...please select curces representing fastener location")
	print("There are " + str(len(faces)) + " faces in the model")

	curveD={}

	ConnectorPointL=[]
	for curve in curves:
		
		tmp_mesh_cnt_curves=apex.EntityCollection()
		# omit curves that are alreadt processed (these are highlighted)
		# omit curves that are aready in the same part as the parent body
		
		# curve_name=curve.getName()
		# if curve.getPathName().find("ConnectorPoint") > -1:
			# pass
		# elif curve.isHighlighted():
			# pass				
		# else:			
			# try:
				# diamL=curve_name.split('.')[0][-3:]
				# #print(diamL)
				# curveD[curve_name]=float(diamL)/10
				
				# radius=0.5*float(diamL)/10
			# except:
				# print("default radius used")
				# radius=1.25
			
		# check if point intersect exists
		pnts=getCurveFaceIntersect(curve,faces)
		if len(pnts) == 0:
			print("Warning: no points generated")
		elif len(pnts) == 1:
			print("Warning: just one point generated")
			
		else:
			print("There are: " + str(len(pnts)) + " points detected")		
			
			point_list=apex.EntityCollection()
			for point in pnts:
				print(point.getName())
				#Create a mesh control point at point location
				tmp_node=CreateSeedPointFromLocation(point,faces)
				point_list.append(point)
			
			# sort the Connector Points based on their ditance from Curves endpoint
			pointA=curve.getExteriorVertices()[0]
			distL=[]
			for point in point_list:
				tmp_dist=get3dPointDist(point,pointA)
				distL.append(tmp_dist)
			
			indx=sorted(range(len(distL)), key=lambda k: distL[k])
			
			sorted_pointL=[]
			for ind in indx:
				sorted_pointL.append(point_list[ind])
			
			
			ConnectorPointL.append(sorted_pointL)
			
			#highlight the curve
			highlight_stuff(curve,0,255,0)

	

	
			
			
	mesh_cnt_curves=apex.EntityCollection()
	n=0
	for points in ConnectorPointL:
		curve=curves[n]
		nodes_list=apex.EntityCollection()
		#print("node 1", item[0].getId() )
		#print("node 2", item[1].getId() )
		for point in points:
			tmp_node=getNearestNodefromFaces(point,faces)
			nodes_list.append(tmp_node)
		
		create_Multi_Sheet_Connector(nodes_list,ConnectorType,Diameter,a,b,matlName,connector_name=curve.getName())
		n+=1
if __name__ == "__main__":
	main()