# -*- encoding=utf-8 -*-
from yade import pack, ymport, export, geom, bodiesHandling
""" This script demonstrates how to use 2 components of creating packings:

1. packing generators pack.regularHexa, pack.regularOrtho etc. generate vertices and filter them
using predicates. (Note that this will be enhanced to irregular packings in the future)

2. predicates are functors returning True/False for points that are given by the packing generator.
Their names are mostly self-explanatory, see their docstrings for meaning of their arguments.

Predicates can be combined using set arithmetics to get their Intersection (p1 & p2), union (p1 | p2),
difference (p1 - p2) and symmetric difference (XOR, p1 ^ p2). This is demontrated on the head (which
has sphere taken off at the back and also a notch) and the body (with cylidrical hole inside).

"""

rad, gap = .15, .02

#Add material
O.materials.append(FrictMat(young=10e9, poisson=.25, frictionAngle=0.5, density=1e3))

#Parameters, which will be passed into spheres and facets creators
kw = {'material': 0}
kwBoxes = {'color': [1, 0, 0], 'wire': False, 'dynamic': False, 'material': 0}
kwMeshes = {'color': [1, 1, 0], 'wire': True, 'dynamic': False, 'material': 0}

O.bodies.append(
        pack.regularHexa(
                (pack.inSphere((0, 0, 4), 2) - pack.inSphere((0, -2, 5), 2)) &
                pack.notInNotch(centerPoint=(0, 0, 4), edge=(0, 1, 0), normal=(-1, 1, -1), aperture=.2),
                radius=rad,
                gap=gap,
                color=(0, 1, 0),
                material=0
        )  # head
        + [
                sphere((.8, 1.9, 5), radius=.2, color=(.6, .6, .6), material=0),
                sphere((-.8, 1.9, 5), radius=.2, color=(.6, .6, .6), material=0),
                sphere((0, 2.4, 4), radius=.4, color=(1, 0, 0), material=0)
        ]  # eyes and nose
        + pack.regularHexa(pack.inCylinder((-1, 2.2, 3.3), (1, 2.2, 3.3), 2 * rad), radius=rad, gap=gap / 3, color=(0.929, 0.412, 0.412), material=0)  #mouth
)
groundId = O.bodies.append(facet([(12, 0, -6), (
        0,
        12,
        -6,
), (-12, -12, -6)], dynamic=False))  # ground

for part in [
        pack.regularHexa(
                pack.inAlignedBox((-2, -2, -2), (2, 2, 2)) - pack.inCylinder((0, -2, 0), (0, 2, 0), 1), radius=1.5 * rad, gap=2 * gap, color=(1, 0, 1), **kw
        ),  # body,
        pack.regularOrtho(pack.inEllipsoid((-1, 0, -4), (1, 1, 2)), radius=rad, gap=0, color=(0, 1, 1), **kw),  # left leg
        pack.regularHexa(pack.inCylinder((+1, 1, -2.5), (0, 3, -5), 1), radius=rad, gap=gap, color=(0, 1, 1), **kw),  # right leg
        pack.regularHexa(pack.inHyperboloid((+2, 0, 1), (+6, 0, 0), 1, .5), radius=rad, gap=gap, color=(0, 0, 1), **kw),  # right hand
        pack.regularOrtho(pack.inCylinder((-2, 0, 2), (-5, 0, 4), 1), radius=rad, gap=gap, color=(0, 0, 1), **kw)  # left hand
]:
	O.bodies.appendClumped(part)

# Example of geom.facetBox usage
oriBody = Quaternion(Vector3(0, 0, 1), (pi / 3))
O.bodies.append(geom.facetBox((12, 0, -6 + 0.9), (1, 0.7, 0.9), oriBody, **kwBoxes))

oriBody = Quaternion(Vector3(0, 0, 1), (pi / 2))
O.bodies.append(geom.facetBox((0, 12, -6 + 0.9), (1, 0.7, 0.9), oriBody, **kwBoxes))

oriBody = Quaternion(Vector3(0, 0, 1), (pi))
O.bodies.append(geom.facetBox((-12, -12, -6 + 0.9), (1, 0.7, 0.9), oriBody, **kwBoxes))

# Example of geom.facetParallelepiped usage
oriBody = Quaternion(Vector3(0, 0, 1), (pi / 3))
O.bodies.append(geom.facetParallelepiped(center=Vector3(12, 0, -6 + 2.7), extents=Vector3(1, 0.7, 0.9), height=0.5, orientation=oriBody, **kwBoxes))

oriBody = Quaternion(Vector3(0, 0, 1), (pi / 2))
O.bodies.append(geom.facetParallelepiped(center=Vector3(0, 12, -6 + 2.7), extents=Vector3(1, 0.7, 0.9), height=0.5, orientation=oriBody, **kwBoxes))

oriBody = Quaternion(Vector3(0, 0, 1), (pi))
O.bodies.append(geom.facetParallelepiped(center=Vector3(-12, -12, -6 + 2.7), extents=Vector3(1, 0.7, 0.9), height=0.5, orientation=oriBody, **kwBoxes))

# Example of geom.facetCylinder, facetHelix and RotationEngine usage example
oriBody = Quaternion(Vector3(1, 0, 0), (pi / 2.0))
rotateIDs = O.bodies.append(
        geom.facetHelix(
                (-7.0, -6.0, -5.0), radiusOuter=2.0, radiusInner=0.1, pitch=2.0, orientation=oriBody, segmentsNumber=50, angleRange=[pi * 8.0, 0], **kwBoxes
        )
)
O.bodies.append(geom.facetCylinder((-7.0, -12.0, -5.0), radius=2.0, height=7.0, orientation=oriBody, segmentsNumber=10, wallMask=4, **kwMeshes))
O.bodies.append(geom.facetCylinder((-7.0, -7.0, -5.0), radius=2.0, height=4.0, segmentsNumber=10, wallMask=4, angleRange=[-pi * 0.2, pi * 1.2], **kwMeshes))

oriBody = Quaternion(Vector3(0, 0, 1), (pi / 2))
O.bodies.append(ymport.gmsh('cone.mesh', orientation=oriBody, **kwMeshes))  #generates facets from the mesh file

SpheresID = []
oriBody = Quaternion(Vector3(0, 0, 1), (pi / 2))
SpheresID += O.bodies.append(ymport.gengeoFile('LSMGenGeo.geo', shift=Vector3(-7.0, -7.0, 0.0), scale=1.0, orientation=oriBody, color=(1, 0, 1), **kw))

#Demonstration of spheresPackDimensions function. The "Edge" particles are colored with blue color
geometryParameters = bodiesHandling.spheresPackDimensions(SpheresID)
for v in [geometryParameters['minId'], geometryParameters['maxId']]:
	for i in v:
		O.bodies[int(i)].shape.color = Vector3(0, 0, 1)

#Example of bodiesHandling.spheresModify()
hat = O.bodies.append(pack.regularOrtho(pack.inCylinder((0, 0, 6), (0, 0, 7), 20 * rad), radius=0.2, gap=0, color=(1, 0, 0)))  # hat
oriBody = Quaternion(Vector3(0, 1, 0), (pi / 8))
hat_upper = O.bodies.append(
        bodiesHandling.spheresModify(hat, shift=(0.0, 0.0, 1.4), scale=0.7, orientation=oriBody, copy=True)
)  #Duplicate the "heart", shifting, scaling and rotating it

#change the color of upper part of the hat
for hatTmp in hat_upper:
	O.bodies[hatTmp].shape.color = (0.9, 0.5, 0.59)

#facetBunker Demonstration
#Demonstration of HarmonicMotionEngine
vibrationPlate = O.bodies.append(
        geom.facetBunker(
                (-7.0, -7.0, -3.0),
                dBunker=geometryParameters['extends'][0] * 1.1,
                dOutput=3.0,
                hBunker=11.0,
                hOutput=1.5,
                hPipe=0.8,
                wallMask=5,
                segmentsNumber=20,
                **kwMeshes
        )
)

# spheresToFile saves coordinates and radii of all spheres of the simulation into the text file, works but disabled. Please, uncomment it, if you need
#print "Saved into the OutFile " + str (export.text("OutFile")) + " spheres";

# spheresFromFile function imports coordinates and radii of all spheres of the simulation into the text file
O.bodies.append(ymport.text('foo.spheres', shift=Vector3(6.0, 6.0, -2.9), scale=0.7, color=(1, 1, 1), **kw))

#Demonstration of HarmonicRotationEngine
O.bodies.append(pack.regularHexa(pack.inSphere((-15, 5, -5), 1.5), radius=rad * 2.0, gap=rad / 3.0, color=(0.5, 0.5, 0.1), material=0))
O.bodies.append(geom.facetBox((-15, 5, -5), (2, 2, 2), wallMask=15, **kwMeshes))
vibrationRotationPlate = O.bodies.append(geom.facetBox((-15, 5, -5), (2, 2, 2), wallMask=16, **kwBoxes))

O.bodies.append(wall((0, 0, -10), axis=2))

O.engines = [
        #SubdomainBalancer(colorize=True,initRun=True,iterPeriod=100),
        ForceResetter(),
        InsertionSortCollider([Bo1_Sphere_Aabb(), Bo1_Facet_Aabb(), Bo1_Wall_Aabb()], label='collider'),
        InteractionLoop(
                [Ig2_Sphere_Sphere_ScGeom(), Ig2_Facet_Sphere_ScGeom(), Ig2_Wall_Sphere_ScGeom()],
                [Ip2_FrictMat_FrictMat_FrictPhys()],
                [Law2_ScGeom_FrictPhys_CundallStrack()],
        ),
        NewtonIntegrator(damping=.1, exactAsphericalRot=True, gravity=(1e-2, 1e-2, -1000)),
        RotationEngine(ids=rotateIDs, angularVelocity=100.0, rotationAxis=[0, -1, 0], rotateAroundZero=1, zeroPoint=[-7.0, -6.0, -5.0]),
        HarmonicMotionEngine(A=[0, 0, 0.5], f=[0, 0, 20.0], fi=[0.0, 0.0, pi], ids=vibrationPlate),
        HarmonicRotationEngine(
                A=0.2, f=20.0, fi=pi, rotationAxis=[1.0, 0.0, 0.0], rotateAroundZero=True, zeroPoint=[-15.0, 3.0, -7.0], ids=vibrationRotationPlate
        ),
        BoxFactory(
                maxParticles=300,
                extents=(1.0, 1.0, 1.0),
                center=(0.0, 12.0, 0.0),
                vMin=200.0,
                vMax=250.0,
                PSDsizes=(0.1, 0.2, 0.3, 0.5, 0.7),
                PSDcum=(0.1, 0.5, 0.8, 1.0),
                PSDcalculateMass=True,
                exactDiam=False,
                vAngle=pi / 3.0,
                massFlowRate=50000.0,
                normal=(0.0, 0.0, 1.0),
                label='factory',
                mask=7,
                silent=True,
                stopIfFailed=False
        )
]
'''
Boxfactory is an example of usage SpheresFactory. Produces PSD-dispersion:

Size:       Mass, %      Cumulative   Cum. mass
                         mass,% 
0.5-0.7     20%          100%         1.0
0.3-0.5     30%          80%          0.8
0.2-0.3     40%          50%          0.5
0.1-0.2     10%          10%          0.1
'''

# we don't care about physical accuracy here, (over)critical step is fine as long as the simulation doesn't explode
O.dt = PWaveTimeStep()
O.run(1, True)
try:
	from yade import qt
	qt.Controller()
	qt.View()
except ImportError:
	pass
O.saveTmp()
O.timingEnabled = True
#O.run(10000,True)
#from yade import timing
#timing.stats()
#quit()
