#(c) 2016 R. T. LGPL: part of Flamingo tools w.b. for FreeCAD

__title__="polarUtils functions"
__author__="oddtopus"
__url__="github.com/oddtopus/flamingo"
__license__="LGPL 3"

import math, FreeCAD, FreeCADGui, frameCmd
from os.path import join, dirname, abspath
from pivy import coin
from frameForms import prototypeDialog

def cerchio(R=1, nseg=8):
  'arg1=R, arg2=nseg: returns a list of 3-uple for nseg+1 coordintates on the semi-circle centered in (0,0,0)'
  teta=[i*2*math.pi/nseg for i in range(nseg+1)]
  return [polar2xy(R,t) for t in teta]

def polar2xy(ro,teta):
  'arg1=ro1(length), arg2=teta(deg): returns [x,y,0]'
  return ro*math.cos(teta),ro*math.sin(teta),0

def getFromFile():
  '''Input polar coord. from .csv: returns a list of (x,y,0).
  The file must be ";" separated: column A = radius, column B = angle'''
  from PySide import QtGui
  fileIn=open(QtGui.QFileDialog().getOpenFileName()[0],'r')
  lines=fileIn.readlines()
  fileIn.close()
  xyCoord=[]
  for line in lines:
    polarCoord=line.split(';')
    xyCoord.append(polar2xy(float(polarCoord[0]),float(polarCoord[1])*math.pi/180))
  return xyCoord
  
def disegna(sk, pos):
  'arg1=sketch, arg2=pos (list of 3-uple): draws the segments of "pos" in "sketch" and close the polygon'
  import FreeCAD, Part, Sketcher
  lines=[]
  while len(pos)>1:
    lines.append(sk.addGeometry(Part.Line(FreeCAD.Vector(pos[0]),FreeCAD.Vector(pos[1]))))
    pos.pop(0)
  for i in range(len(lines)-1):
    sk.addConstraint(Sketcher.Constraint('Coincident',lines[i],2,lines[i+1],1))
  sk.addConstraint(Sketcher.Constraint('Coincident',lines[len(lines)-1],2,lines[0],1))
  FreeCAD.activeDocument().recompute()
  return lines

def setWP():
  'function to change working plane'
  import FreeCAD, FreeCADGui, frameCmd
  normal=point=None
  curves=[]
  straight=[]
  Z=FreeCAD.Vector(0,0,1)
  for edge in frameCmd.edges():
    if edge.curvatureAt(0)!=0:
      curves.append(edge)
    else:
      straight.append(edge)
  # define normal: 1st from face->2nd from curve->3rd from straight edges
  if frameCmd.faces():
    normal=frameCmd.faces()[0].normalAt(0,0)
  elif curves:
    normal=curves[0].tangentAt(0).cross(curves[0].normalAt(0))
  elif len(straight)>1:
    if straight and not frameCmd.isParallel(straight[0].tangentAt(0),straight[1].tangentAt(0)):
      normal=straight[0].tangentAt(0).cross(straight[1].tangentAt(0))
  elif FreeCADGui.Selection.getSelection():
    normal=FreeCAD.DraftWorkingPlane.getRotation().multVec(Z)
  else:
    normal=Z
  # define point: 1st from vertex->2nd from centerOfCurvature->3rd from intersection->4th from center of edge
  points=[v.Point for sx in FreeCADGui.Selection.getSelectionEx() for v in sx.SubObjects if v.ShapeType=='Vertex']
  if not points:
    points=[edge.centerOfCurvatureAt(0) for edge in curves]
  if not points and len(straight)>1:
    inters=frameCmd.intersectionCLines(straight[0],straight[1])
    if inters:
      points.append(inters)
  if not points and len(straight):
    points.append(straight[0].CenterOfMass)
  if points:
    point=points[0]
  else:
    point=FreeCAD.Vector()
  # move the draft WP
  FreeCAD.DraftWorkingPlane.alignToPointAndAxis(point,normal)
  FreeCADGui.Snapper.setGrid()
def rotWP(ax=None,ang=45):
  import FreeCAD, FreeCADGui
  if not ax:
    ax=FreeCAD.Vector(0,0,1)
  if hasattr(FreeCAD,'DraftWorkingPlane') and hasattr(FreeCADGui,'Snapper'):
    pl=FreeCAD.DraftWorkingPlane.getPlacement()
    pRot=FreeCAD.Placement(FreeCAD.Vector(),FreeCAD.Rotation(ax,ang))
    newpl=pl.multiply(pRot)
    FreeCAD.DraftWorkingPlane.setFromPlacement(newpl)
    FreeCADGui.Snapper.setGrid()
  return newpl
def offsetWP(delta):
  import FreeCAD,FreeCADGui
  if hasattr(FreeCAD,'DraftWorkingPlane') and hasattr(FreeCADGui,'Snapper'):
    rot=FreeCAD.DraftWorkingPlane.getPlacement().Rotation
    offset=rot.multVec(FreeCAD.Vector(0,0,delta))
    point=FreeCAD.DraftWorkingPlane.getPlacement().Base+offset
    FreeCAD.DraftWorkingPlane.alignToPointAndAxis(point,offset)
    FreeCADGui.Snapper.setGrid()
  
class arrow(object):
  '''
  This class draws a green arrow to be used as an auxiliary compass
  to show position and orientation of objects.
    arrow(pl=None, scale=[100,100,20],offset=100,name='ARROW')
  '''
  def __init__(self,pl=None, scale=[100,100,20],offset=100,name='ARROW'):
    # define main properties
    self.view=FreeCADGui.ActiveDocument.ActiveView
    self.sg=self.view.getSceneGraph()
    self.cb=self.view.addEventCallbackPivy(coin.SoMouseButtonEvent.getClassTypeId(), self.pickCB)
    # define OpenInventor properties
    self.node=coin.SoSeparator() #self.node=coin.SoSelection()
    self.name=name
    self.node.setName(self.name)
    self.color=coin.SoBaseColor(); self.color.rgb=0,0.8,0
    self.transform=coin.SoTransform(); self.transform.scaleFactor.setValue(scale)
    self.cone=coin.SoCone()
    # create children of node
    self.node.addChild(self.color)
    self.node.addChild(self.transform)
    self.node.addChild(self.cone)
    # draw the arrow and move it to its Placement with the specified offset
    self.sg.addChild(self.node)
    self.offset=offset
    if not pl:
      pl=FreeCAD.Placement()
    self.moveto(pl)
  def closeArrow(self):
    self.view.removeEventCallbackPivy(coin.SoMouseButtonEvent.getClassTypeId(), self.cb)
    self.sg.removeChild(self.node)
  def moveto(self,pl):
    import FreeCAD
    rotx90=FreeCAD.Base.Rotation(FreeCAD.Vector(0,1,0),FreeCAD.Vector(0,0,1))
    self.Placement=pl
    self.transform.rotation.setValue(tuple(self.Placement.Rotation.multiply(rotx90).Q))
    offsetV=self.Placement.Rotation.multVec(FreeCAD.Vector(0,0,self.offset))
    self.transform.translation.setValue(tuple(self.Placement.Base+offsetV))
  def pickCB(self, ecb):
    event=ecb.getEvent()
    arg=None
    if event.getState()==coin.SoMouseButtonEvent.DOWN:
      render_manager=self.view.getViewer().getSoRenderManager()
      doPick=coin.SoRayPickAction(render_manager.getViewportRegion())
      doPick.setPoint(coin.SbVec2s(*ecb.getEvent().getPosition()))
      doPick.apply(render_manager.getSceneGraph())
      points=doPick.getPickedPointList()
      if points.getLength():
        path=points[0].getPath()
        if str(tuple(path)[-2].getName())==self.name: self.pickAction(path,event,arg)
  def pickAction(self,path=None,event=None,arg=None): # sample
    if path:
      for n in path: 
        if str(n.getName())==self.name: 
          FreeCAD.Console.PrintMessage("You hit the %s\n"%self.name) #replace here the action to perform
    self.closeArrow()

class arrow_move(arrow):
  '''
  arrow_move(pl=None,direct=None,M=100)
    pl: the placement of the arrow
    direct: the displacement vector
    M: the scale of display
  This class derives from "arrow" and adds the ability to move the selected
  objects when the arrow is picked.  This is accomplished by redefining the
  method "pickAction".
  '''
  def __init__(self, edit, pl=None, direct=None, M=100.0, objs=None):
    self.objs=objs
    self.edge=None
    self.edit=edit
    # define direction
    self.scale=M
    if direct: self.direct=direct
    else: self.direct=FreeCAD.Vector(0,0,1)*M
    # define placement
    if not pl:pl=FreeCAD.Placement()
    # draw arrow
    super(arrow_move,self).__init__(pl=pl, scale=[M/2,M/2,M/10], offset=M/2)
  def pickAction(self,path=None,event=None,arg=None):
    FreeCAD.activeDocument().openTransaction('Quick move')
    if event.wasCtrlDown(): k=-1*float(self.edit.text())
    else: k=1*float(self.edit.text())
    sel=FreeCADGui.Selection.getSelection()
    if sel: self.objs=[o for o in sel if hasattr(o,'Shape')]
    if event.wasCtrlDown() and event.wasAltDown():
      if frameCmd.edges(): 
        self.edge=frameCmd.edges()[0]
        for o in self.objs: frameCmd.rotateTheBeamAround(o, self.edge, 90)
      elif self.edge:
        for o in self.objs: frameCmd.rotateTheBeamAround(o, self.edge, 90)
    else:
      for o in self.objs: o.Placement.move(self.direct*k)
      self.Placement.move(self.direct*k)
      pl,direct,M=[self.Placement,self.direct,self.scale]
      self.closeArrow()
      self.__init__(self.edit, pl,direct,M,self.objs)
    FreeCAD.activeDocument().commitTransaction()

class handleDialog(prototypeDialog):
  def __init__(self):
    self.arrow=None
    super(handleDialog,self).__init__('disp.ui')
    self.form.edit1.setValidator(QDoubleValidator())
    #self.form.edit1.hide()#
    #self.form.lab1.hide()#
    self.form.btn1.clicked.connect(self.selectAction)
    from sys import platform
    if platform.startswith('win'):
      self.form.lab2.hide()
  def selectAction(self):
    self.objs=FreeCADGui.Selection.getSelection()
    L=direct=None
    pl=FreeCAD.Placement()
    # define general size of arrow
    if self.arrow: self.arrow.closeArrow()
    M=100.0
    moveSet=[o for o in FreeCADGui.Selection.getSelection() if hasattr(o,'Shape')]
    if moveSet:
      bb=moveSet[0].Shape.BoundBox
      for o in moveSet: bb=bb.united(o.Shape.BoundBox)
      edgesLens=[e.Length for o in moveSet for e in o.Shape.Edges]
      M=(bb.XLength+bb.YLength+bb.ZLength)/6.0
      # define placement of arrow
      orig=bb.Center
      orig[2]=bb.ZMax+bb.ZLength*0.1
      pl.move(orig)
    # define direction and displacement
    #if self.form.edit1.text(): L=float(self.form.edit1.text())
    if frameCmd.faces():
      direct=frameCmd.faces()[0].normalAt(0,0)
      #f=frameCmd.faces()[0]
      #if L: direct=f.normalAt(0,0).normalize()*L
      #else: direct=f.normalAt(0,0).normalize()*math.sqrt(f.Area)
    elif frameCmd.edges():
      direct=frameCmd.edges()[0].tangentAt(0)
      #e=frameCmd.edges()[0]
      #if L: direct=e.tangentAt(0)*L
      #else: direct=e.tangentAt(0).multiply(e.Length)
    # create the arrow_move object
    if direct: 
      pl.Rotation=FreeCAD.Rotation(FreeCAD.Vector(0,0,1),direct).multiply(pl.Rotation)
      self.arrow=arrow_move(self.form.edit1,pl=pl,direct=direct,M=M,objs=self.objs)
  def accept(self):
    self.reject()
  def reject(self):
    if self.arrow: self.arrow.closeArrow()
    try: self.view.removeEventCallback('SoEvent',self.call)
    except: pass
    if FreeCAD.ActiveDocument: FreeCAD.ActiveDocument.recompute()
    FreeCADGui.Control.closeDialog()

class label3D(object):
  '''
  This class writes a 2D label in the 3D viewport.
  To be used as an auxiliary tool to show flags during execution
  of commands.
  Note: default text size is 7 units.
    label3D(pl=None, scale=[1,1,1], color=(1.0,0.6,0.0), text='TEXT')
  '''
  def __init__(self,pl=None, sizeFont=30, color=(1.0,0.6,0.0), text='TEXT'):
    import FreeCAD, FreeCADGui
    self.node=coin.SoSeparator()
    self.color=coin.SoBaseColor()
    self.color.rgb=color
    self.node.addChild(self.color)
    self.transform=coin.SoTransform()
    self.node.addChild(self.transform)
    self.font=coin.SoFont()
    self.node.addChild(self.font)
    self.font.size=sizeFont
    self.text=coin.SoText2()
    self.text.string=text
    self.node.addChild(self.text)
    self.sg=FreeCADGui.ActiveDocument.ActiveView.getSceneGraph()
    self.sg.addChild(self.node)
    if not pl:
      pl=FreeCAD.Placement()
    self.moveto(pl)
  def removeLabel(self):
    self.sg.removeChild(self.node)
  def moveto(self,pl):
    import FreeCAD
    self.Placement=pl
    self.transform.translation.setValue(tuple(self.Placement.Base))
    self.transform.rotation.setValue(tuple(self.Placement.Rotation.Q))

import DraftTools,Draft,qForms
from PySide.QtGui import *
class hackedLine(DraftTools.Line):
  '''
  One hack of the class DraftTools.Line
  to make 3D drafting easier.
  '''    
  def __init__(self, wireFlag=True):
    DraftTools.Line.__init__(self,wireFlag)
    self.Activated()
    dialogPath=join(dirname(abspath(__file__)),"dialogs","hackedline.ui")
    self.hackedUI=FreeCADGui.PySideUic.loadUi(dialogPath)
    self.hackedUI.btnRot.clicked.connect(self.rotateWP)
    self.hackedUI.btnOff.clicked.connect(self.offsetWP)
    self.hackedUI.btnXY.clicked.connect(lambda: self.alignWP(FreeCAD.Vector(0,0,1)))
    self.hackedUI.btnXZ.clicked.connect(lambda: self.alignWP(FreeCAD.Vector(0,1,0)))
    self.hackedUI.btnYZ.clicked.connect(lambda: self.alignWP(FreeCAD.Vector(1,0,0)))
    self.ui.layout.addWidget(self.hackedUI)
  def alignWP(self, norm):
      FreeCAD.DraftWorkingPlane.alignToPointAndAxis(self.node[-1],norm)
      FreeCADGui.Snapper.setGrid()
  def offsetWP(self):
    if hasattr(FreeCAD,'DraftWorkingPlane') and hasattr(FreeCADGui,'Snapper'):
      s=FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Draft").GetInt("gridSize")
      sc=[float(x*s) for x in [1,1,.2]]
      varrow =arrow(FreeCAD.DraftWorkingPlane.getPlacement(),scale=sc,offset=s)
      offset=QInputDialog.getInteger(None,'Offset Work Plane','Offset: ')
      if offset[1]:
        offsetWP(offset[0])
      FreeCADGui.ActiveDocument.ActiveView.getSceneGraph().removeChild(varrow.node)
  def rotateWP(self):
    self.form=qForms.rotWPForm()
  def action(self,arg): #re-defintition of the method of parent
      "scene event handler"
      if arg["Type"] == "SoKeyboardEvent" and arg["State"]=='DOWN':
          # key detection
          if arg["Key"] == "ESCAPE":
              self.finish()
          elif arg["ShiftDown"] and arg["CtrlDown"]:
            if arg["Key"] in ('M','m'):
              if self.hackedUI.cb1.isChecked():
                self.hackedUI.cb1.setChecked(False)
              else:
                self.hackedUI.cb1.setChecked(True)
            elif arg["Key"] in ('O','o'):
              self.offsetWP()
            elif arg["Key"] in ('R','r'):
              self.rotateWP()
      elif arg["Type"] == "SoLocation2Event":
          # mouse movement detection
          self.point,ctrlPoint,info = DraftTools.getPoint(self,arg)
      elif arg["Type"] == "SoMouseButtonEvent":
          # mouse button detection
          if (arg["State"] == "DOWN") and (arg["Button"] == "BUTTON1"):
              if (arg["Position"] == self.pos):
                  self.finish(False,cont=True)
              else:
                  if (not self.node) and (not self.support):
                      DraftTools.getSupport(arg)
                      self.point,ctrlPoint,info = DraftTools.getPoint(self,arg)
                  if self.point:
                      self.ui.redraw()
                      self.pos = arg["Position"]
                      self.node.append(self.point)
                      self.drawSegment(self.point)
                      if self.hackedUI.cb1.isChecked():
                        rot=FreeCAD.DraftWorkingPlane.getPlacement().Rotation
                        normal=rot.multVec(FreeCAD.Vector(0,0,1))
                        FreeCAD.DraftWorkingPlane.alignToPointAndAxis(self.point,normal)
                        FreeCADGui.Snapper.setGrid()
                      if (not self.isWire and len(self.node) == 2):
                          self.finish(False,cont=True)
                      if (len(self.node) > 2):
                          if ((self.point-self.node[0]).Length < Draft.tolerance()):
                              self.undolast()
                              self.finish(True,cont=True)