Macro Unroll Ruled Surface

From FreeCAD Documentation
Revision as of 19:48, 13 September 2013 by Donovae (talk | contribs) (→‎Python Code)

File:Text-x-python unrollRuledSurface_Macro

Description
The macro allows to unroll ruled surfaces and to draw them on a page.

Author: Hervé B.
Author
Hervé B.
Download
None
Links
Macro Version
1.0
Date last modified
None
FreeCAD Version(s)
None
Default shortcut
None
See also
None


Installation

 * Copy the code file of the macro in the dircetory (Linux) : $home/.Freecad/Mod/UnrollRuledSurface.
 * Add templates : A3_Landscape_Empty.svg  A3_Landscape.svg  A4_Landscape_Empty.svg  A4_Landscape.svg

Cf [1]


Options

 * Number of generatrix
 * Scale manual or automatic
 * Page format: a3/a4, cartridge (cf FreeCAD templates)
 * Group drawings in the same page as possible.

Instruction for use

 * Select ruled surfaces
 * Explode them (cf Draft menu)
 * Select the surfaces
 * Execute the macro

Python Code

#***************************************************************************
#*                                                                         *
#*   Copyright (c) 2013 - DoNovae/Herve BAILLY <hbl13@donovae.com>           *
#*                                                                         *
#*   This program is free software; you can redistribute it and/or modify  *
#*   it under the terms of the GNU Lesser General Public License (LGPL)    *
#*   as published by the Free Software Foundation; either version 2 of     *
#*   the License, or (at your option) any later version.                   *
#*   for detail see the LICENCE text file.                                 *
#*                                                                         *
#*   This program is distributed in the hope that it will be useful,       *
#*   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
#*   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
#*   GNU Library General Public License for more details.                  *
#*                                                                         *
#*   You should have received a copy of the GNU Library General Public     *
#*   License along with this program; if not, write to the Free Software   *
#*   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  *
#*   USA                                                                   *
#*                                                                         *
#***************************************************************************

#####################################
# Macro UnrollRuledSurface
#     Unroll of a ruled surface
#####################################
import FreeCAD , FreeCADGui , Part, Draft, math, Drawing , PyQt4, os
from PyQt4 import QtGui,QtCore
from FreeCAD import Base
fields_l = [] 
unroll_l = [] 

#####################################
#####################################
# Functions 
#####################################
#####################################

#####################################
# Function errorDialog 
#####################################
def errorDialog(msg):
    diag = QtGui.QMessageBox(QtGui.QMessageBox.Critical,u"Error Message",msg )
    diag.setWindowFlags(PyQt4.QtCore.Qt.WindowStaysOnTopHint)
    diag.exec_()


#####################################
# Function proceed 
#####################################
def proceed():
   QtGui.qApp.setOverrideCursor(QtCore.Qt.WaitCursor)

   FreeCAD.Console.PrintMessage("===========================================\n")
   FreeCAD.Console.PrintMessage("UnrollRuledSurface: start.\n")
   try:
      file_name  = fields_l[0].text()
      pts_nbr    = float(fields_l[1].text())
      scale    = float(fields_l[2].text())
      scale_auto = scale_check.isChecked()
      a3 = a3_check.isChecked()
      cartridge = cartridge_check.isChecked()
      onedrawing = onedrawing_check.isChecked()
      FreeCAD.Console.PrintMessage("UnrollRuledSurface.file_name: "+file_name+"\n")
      FreeCAD.Console.PrintMessage("UnrollRuledSurface.pts_nbr: "+str(pts_nbr)+"\n")
      FreeCAD.Console.PrintMessage("UnrollRuledSurface.scale: "+str(scale)+"\n")
      FreeCAD.Console.PrintMessage("UnrollRuledSurface.scale_check: "+str(scale_auto)+"\n")
      FreeCAD.Console.PrintMessage("UnrollRuledSurface.a3_check: "+str(a3)+"\n")
      FreeCAD.Console.PrintMessage("UnrollRuledSurface.cartridge: "+str(cartridge)+"\n")
      FreeCAD.Console.PrintMessage("UnrollRuledSurface.onedrawing: "+str(onedrawing)+"\n")
   except:
      msg="UnrollRuledSurface: wrong inputs...\n"
      FreeCAD.Console.PrintError(msg)
      errorDialog(msg)

   QtGui.qApp.restoreOverrideCursor()
   DialogBox.hide()
   draw=Drawing2d( scale, scale_auto , a3 , cartridge , onedrawing ) 
   unrollRS=unrollRuledSurface( file_name , pts_nbr )
   #
   # Get selection
   #
   sel=FreeCADGui.Selection.getSelection()
   faceid=0
   for objid in range( sel.__len__() ):
     shape=sel[objid].Shape
     faces=shape.Faces
     for id in range( faces.__len__() ):
        FreeCAD.Console.PrintMessage("UnrollRuledSurface.proceed: ObjId= "+str(objid)+" , faceId= "+str( faceid )+"\n")
        unroll_l.append( unrollRS.unroll(faces[id]) )
        faceid=faceid+1
   draw.all( unroll_l )

   FreeCAD.Console.PrintMessage("UnrollRuledSurface: end.\n")
   FreeCAD.Console.PrintMessage("===========================================\n")

#####################################
# Function close 
#####################################
def close():
   DialogBox.hide()

#####################################
# Class unrollRuledSurface 
#     - file_name : ouput file 
#     - pts_nbr : nbr point of 
#       discretization
#####################################
class unrollRuledSurface:
  def __init__( self, file_name, pts_nbr ):
    self.doc = FreeCAD.newDocument( str(file_name) )
    self.file_name = file_name
    self.pts_nbr = int(pts_nbr)
    FreeCAD.Console.PrintMessage("UnrollRuledSurface.unroll - file_name: "+self.file_name+" , pts_nbr: "+str(self.pts_nbr)+"\n")


  #####################################
  # Function discretize 
  #####################################
  def discretize(self,curve):
         if type(curve).__name__=='GeomLineSegment':
            sd=curve.discretize( self.pts_nbr )
         elif type(curve).__name__=='GeomBSplineCurve':
            nodes=curve.getPoles()
            spline=Part.BSplineCurve()
            spline.buildFromPoles( nodes )
            sd=spline.discretize( self.pts_nbr )
         elif type(curve).__name__=='GeomCircle':
            sd=curve.discretize( self.pts_nbr )
	 else:
            sd=curve.discretize( self.pts_nbr )
         return sd 

  #####################################
  # Function discretize 
  #####################################
  def nbpoles(self,curve):
       if type(curve).__name__=='GeomLineSegment':
         nbpol=0
       elif type(curve).__name__=='GeomBSplineCurve':
         nbpol=curve.NbPoles
       elif type(curve).__name__=='GeomCircle':
         nbpol=1
       else:
         nbpol=0
       return nbpol

  #####################################
  # Function unroll 
  #####################################
  # Unroll of a face 
  # composed of 2 or 4 edges
  #####################################
  def unroll(self,face):
    FreeCAD.Console.PrintMessage("UnrollRuledSurface.unroll: Ege Nbr= "+str( face.Edges.__len__())+"\n")
    if face.Edges.__len__() == 2: 
       e1=face.Edges[0]
       e2=face.Edges[1]
       sd1=e1.Curve.discretize( self.pts_nbr )
       sd2=e2.Curve.discretize( self.pts_nbr )
    elif face.Edges.__len__() == 3:
       e1=face.Edges[0]
       e2=face.Edges[2]
       sd1=e1.Curve.discretize( self.pts_nbr )
       sd2=e2.Curve.discretize( self.pts_nbr )
    else:
       E0=face.Edges[0]
       E1=face.Edges[1]
       E2=face.Edges[2]
       E3=face.Edges[3]
       #
       # Choose more complexe curve as edge
       #
       nbpol0=self.nbpoles(E0.Curve)
       nbpol1=self.nbpoles(E1.Curve)
       nbpol2=self.nbpoles(E2.Curve)
       nbpol3=self.nbpoles(E3.Curve)

       if nbpol0 + nbpol2 > nbpol1 + nbpol3:
         e1=E0
         e2=E2
         v=self.discretize( E1 )
	 v0=v[0]
	 v1=v[self.pts_nbr-1]
       else:
         e1=E1
         e2=E3
         v=self.discretize( E2 )
	 v0=v[0]
	 v1=v[self.pts_nbr-1]

       sd1=self.discretize( e1 )
       sd2=self.discretize( e2 )
       #
       # Reverse if curves cross over
       #
       if not ( sd2[0].__eq__( v0 ) or not sd2[0].__eq__( v1 ) ):
          sd2.reverse()

    #
    # Create a polygon object and set its nodes 
    #
    devlxy_l=self.devlxyz( sd1 , sd2 )
    FreeCAD.Console.PrintMessage("UnrollRuledSurface.unroll: size devlxy_l: "+str( devlxy_l.__len__())+"\n")
    p=self.doc.addObject("Part::Polygon","Polygon") 
    p.Nodes=devlxy_l
    self.doc.recompute()
    FreeCADGui.SendMsgToActiveView("ViewFit")
    return p

  #####################################
  # Function vect_copy 
  #   - vect:  
  #   - return copy of vector
  #####################################
  def vect_copy( self, vect):
     v= vect + FreeCAD.Base.Vector(0,0,0)
     return v 

  #####################################
  # Function vect_cos 
  #   - vect1,2:  
  #   - return cos angle between 
  #     2 vectors 
  #####################################
  def vect_cos( self , vect1, vect2 ):
     cosalp=vect1.dot(vect2)/vect1.Length/vect2.Length
     return cosalp
  
  #####################################
  # Function vect_sin 
  #   - vect1,2:  
  #   - return abs(sin) angle between 
  #     2 vectors 
  #####################################
  def vect_sin( self , vect1, vect2 ):
     v= FreeCAD.Base.Vector(0,0,0)
     v.x=vect1.y*vect2.z-vect1.z*vect2.y
     v.y=vect1.z*vect2.x-vect1.x*vect2.z
     v.z=vect1.x*vect2.y-vect1.y*vect2.x
     sinalp=v.Length/vect1.Length/vect2.Length
     return sinalp

  
  #####################################
  # Function devlxyz 
  #    - vect1,2: 2 edges of the shape
  #    - return dvlxy_l
  #####################################
  # unroll of a face 
  # composed of 4 edges
  #####################################
  def devlxyz( self , vect1 , vect2 ):
    #
    # Init
    #
    if ( vect1.__len__() != vect2.__len__()) or  ( vect1.__len__() != self.pts_nbr ) or ( vect2.__len__() != self.pts_nbr ):
        msg="UnrollRuledSurface.devlxyz: incompatility of sizes vect1 , vect2, pts_nbr- "+str( vect1.__len__())+" , "+str( vect2.__len__())+" , "+str( self.pts_nbr )+"\n"
        FreeCAD.Console.PrintError(msg)
        errorDialog(msg)

    devlxy_l=[]
    devl1xy_l=[]
    devl2xy_l=[]
    errormax=0.0
    #
    # Init unroll
    # AB
    #
    a1b1=vect2[0].sub(vect1[0])
    oa1=FreeCAD.Vector(0,0,0)
    devl1xy_l.append( oa1 ) #A1
    ob1=FreeCAD.Vector(a1b1.Length,0,0)
    devl2xy_l.append( ob1 ) #B1
    for j in range( 1 , self.pts_nbr ) : 
      #
      # AB
      #
      ab=vect2[j-1].sub(vect1[j-1])
      #
      # AC
      #
      ac=vect1[j].sub(vect1[j-1])
      #
      # BD
      #
      bd=vect2[j].sub(vect2[j-1])
      #
      # CD 
      #
      cd=vect2[j].sub(vect1[j])
      #
      # A1B1 in unroll plan
      #
      a1b1=devl2xy_l[j-1].sub(devl1xy_l[j-1])
      a1b1n=self.vect_copy(a1b1)
      a1b1n.normalize()
      a1b1on=FreeCAD.Vector(-a1b1n.y,a1b1n.x,0)
      #
      # A1C1
      #
      cosalp=self.vect_cos( ab , ac )
      sinalp=self.vect_sin( ab , ac )
      a1c1=self.vect_copy(a1b1n)
      a1c1.multiply(cosalp*ac.Length)
      v=self.vect_copy(a1b1on)
      v.multiply(sinalp*ac.Length)
      a1c1=a1c1.add(v)
      oa1=self.vect_copy(devl1xy_l[j-1])
      oc1=oa1.add(a1c1)
      devl1xy_l.append(oc1)
      #
      # B1D1
      #
      cosalp=self.vect_cos( ab , bd )
      sinalp=self.vect_sin( ab , bd )
      b1d1=self.vect_copy(a1b1n)
      b1d1.multiply(cosalp*bd.Length)
      v=self.vect_copy(a1b1on)
      v.multiply(sinalp*bd.Length)
      b1d1=b1d1.add(v)
      ob1=self.vect_copy(devl2xy_l[j-1])
      od1=ob1.add(b1d1)
      devl2xy_l.append(od1)
      #
      # Draw generatrice
      #
      c1d1=devl2xy_l[j].sub( devl1xy_l[j] )
      if ab.Length <> 0 :
         errormax=max(errormax,math.fabs(ab.Length-c1d1.Length)/ab.Length)
    #
    # The end
    #
    FreeCAD.Console.PrintMessage("UnrollRuledSurface Error cd,c1d1: {:.1f} %\n".format(errormax*100))

    #
    # Close polygone
    #
    devlxy_l = devl1xy_l
    devl2xy_l.reverse()
    devlxy_l.extend( devl2xy_l )
    v=FreeCAD.Vector(0,0,0)
    devlxy_l.append( v )

    return devlxy_l

    

  #####################################
  # Function draw_line 
  #   - vect0,1: two points 
  #####################################
  def draw_line( self , vect0 , vect1 ):
     l=Part.Line()
     l.StartPoint=vect0
     l.EndPoint=vect1
     self.doc.addObject("Part::Feature","Line").Shape=l.toShape()


#####################################
# Class Drawing2d 
#####################################
class Drawing2d:
  #####################################
  # Function __init__ 
  #     - Scale
  #     - scale_auto
  #     - a3
  #     - cartridge
  #     - onedrawing
  #####################################
  def __init__( self,  scale , scale_auto , a3 , cartridge , onedrawing ):
    self.TopX_H=0
    self.TopY_H=0
    self.TopX_V=0
    self.TopY_V=0
    self.TopX_Hmax=0
    self.TopY_Hmax=0
    self.TopX_Vmax=0
    self.TopY_Vmax=0
    self.a3=a3
    self.scale=scale
    self.scale_auto=scale_auto
    self.cartridge=cartridge
    self.onedrawing=onedrawing
    if self.a3:
      self.L=420
      self.H=297
      self.marge=6
    else:
      self.L=297
      self.H=210
      self.marge=6
    self.name="Page"

  #####################################
  # Function newPage 
  #####################################
  def newPage( self ):
    freecad_dir=os.getenv('HOME')+"/.FreeCAD/Mod/unrollRuledSurface"
    page = FreeCAD.activeDocument().addObject('Drawing::FeaturePage', self.name )
    if self.a3:
        if self.cartridge:
           page.Template = freecad_dir+'/A3_Landscape.svg'   
        else:
           page.Template = freecad_dir+'/A3_Landscape_Empty.svg'   
    else:
        if self.cartridge:
           page.Template = freecad_dir+'/A4_Landscape.svg'   
        else:
           page.Template = freecad_dir+'/A4_Landscape_Empty.svg'   
    return page


  #####################################
  # Function all 
  #####################################
  def all( self, obj_l ):
    for objid in range( obj_l.__len__() ):
      if objid == 0 or not self.onedrawing:
        page = self.newPage()
      else:
        page = FreeCAD.activeDocument().getObject( self.name )
      self.done( objid , obj_l[objid] , page )

  #####################################
  # Function all 
  #####################################
  def done( self, id , obj , page ):
    #
    # Init
    #
    xmax=obj.Shape.BoundBox.XMax-obj.Shape.BoundBox.XMin
    ymax=obj.Shape.BoundBox.YMax-obj.Shape.BoundBox.YMin
    if ymax > xmax :
      Draft.rotate( obj , 90 )
    Draft.move( obj , FreeCAD.Base.Vector( -obj.Shape.BoundBox.XMin , -obj.Shape.BoundBox.YMin , 0))
    xmax=obj.Shape.BoundBox.XMax-obj.Shape.BoundBox.XMin
    ymax=obj.Shape.BoundBox.YMax-obj.Shape.BoundBox.YMin

    scale=min((self.L-4*self.marge)/xmax,(self.H-4*self.marge)/ymax)

    if ( not self.scale_auto ) or ( self.onedrawing ) :
       scale=self.scale

    FreeCAD.Console.PrintMessage("UnrollRuledSurface.drawing: scale= {:.2f}\n".format(scale))
     

    if id == 0 or not self.onedrawing:
      #
      # Init
      #
      self.TopX_H=self.marge*2
      self.TopY_H=self.marge*2
      TopX=self.TopX_H
      TopY=self.TopY_H
      self.TopX_H=self.TopX_H + xmax * scale + self.marge
      self.TopY_H=self.TopY_H 
      self.TopX_Hmax=max( self.TopX_Hmax , self.TopX_H )
      self.TopY_Hmax=max( self.TopY_Hmax , self.TopY_H + ymax*scale+self.marge )
      self.TopX_Vmax=max( self.TopX_Vmax , self.TopX_Hmax  )
      self.TopX_V=max(self.TopX_Vmax,self.TopX_V)
      self.TopY_V=self.marge*2
    elif self.onedrawing:
      if ( self.TopX_H + xmax * scale < self.L ):
        if ( self.TopY_H + ymax * scale + self.marge*2 < self.H ):
	   #
	   # H Add at right on same horizontal line
	   #
           TopX=self.TopX_H
           TopY=self.TopY_H
           self.TopX_H=self.TopX_H + xmax * scale + self.marge
           self.TopY_H=self.TopY_H 
	   self.TopX_Hmax=max( self.TopX_Hmax , self.TopX_H )
	   self.TopY_Hmax=max( self.TopY_Hmax , self.TopY_H + ymax*scale+self.marge )
	   self.TopX_Vmax=max( self.TopX_Hmax , self.TopX_Vmax )
           self.TopX_Vmax=max( self.TopX_Vmax , self.TopX_Hmax  )
           self.TopX_V=max(self.TopX_Vmax,self.TopX_V)
	else:
	   #
	   # V Add at right on same horizontal line
	   #
           Draft.rotate( obj , 90 )
	   Draft.move( obj , FreeCAD.Base.Vector( -obj.Shape.BoundBox.XMin , -obj.Shape.BoundBox.YMin , 0))
           self.TopX_V=max(self.TopX_Vmax, self.TopX_V)
           TopX=self.TopX_V
           TopY=self.TopY_V
	   self.TopX_V = self.TopX_V + ymax * scale + self.marge
	   self.TopY_Vmax=max( self.TopY_Vmax , self.TopY_V + xmax * scale + self.marge )
      else:
	#
	# H Carriage return 
	#
        if ( self.TopY_Hmax + ymax * scale + self.marge*2 < self.H ):   
           TopX=self.marge*2
           TopY=self.TopY_Hmax
           self.TopX_H=TopX + xmax * scale + self.marge
           self.TopY_H=TopY 
	   self.TopX_Hmax=max( self.TopX_Hmax , self.TopX_H )
	   self.TopY_Hmax=self.TopY_H + ymax*scale+self.marge
           self.TopX_Vmax=max( self.TopX_Vmax , self.TopX_Hmax )
           self.TopX_V=max(self.TopX_Vmax,self.TopX_V)
	else:
	   #
	   # V Add at right on same horizontal line
	   #
           Draft.rotate( obj , 90 )
	   Draft.move( obj , FreeCAD.Base.Vector( -obj.Shape.BoundBox.XMin , -obj.Shape.BoundBox.YMin , 0))
           TopX=self.TopX_V
           TopY=self.TopY_V
	   self.TopX_V = self.TopX_V + ymax * scale + self.marge
	   self.TopY_Vmax=max( self.TopY_Vmax , self.TopY_V + xmax * scale + self.marge )

    TopView = FreeCAD.activeDocument().addObject('Drawing::FeatureViewPart','TopView')
    TopView.Source = obj
    TopView.Direction = (0.0,0.0,1)
    TopView.Rotation = 0 
    TopView.X = TopX 
    TopView.Y = TopY 
    TopView.ShowHiddenLines = False
    TopView.Scale = scale 
    page.addObject(TopView)
    FreeCAD.activeDocument().recompute()




#####################################
#####################################
# Dialog Box 
#####################################
#####################################
fields = [[ "File Name" , "UnrollSurface" ]]
fields.append(["Dicretization Points Nbr","100" ])
fields.append(["Scale","1" ])

DialogBox = QtGui.QDialog()
DialogBox.resize(250,250)
DialogBox.setWindowTitle("UnrollRuledSurface")
la = QtGui.QVBoxLayout(DialogBox)

#
# Input fields
#
for id in range(len( fields )):
  la.addWidget(QtGui.QLabel( fields[ id ][ 0 ] ))
  fields_l.append( QtGui.QLineEdit( fields[ id ][ 1 ] ))
  la.addWidget( fields_l[ id ] )

scale_check = QtGui.QCheckBox( DialogBox )
scale_check.setObjectName("checkBox")
scale_check.setChecked(True)
la.addWidget(QtGui.QLabel("Scale auto"))
la.addWidget(scale_check)

a3_check = QtGui.QCheckBox( DialogBox )
a3_check.setObjectName("checkBox")
la.addWidget(QtGui.QLabel("A3 Format"))
a3_check.setChecked(False)
la.addWidget(a3_check)

cartridge_check = QtGui.QCheckBox( DialogBox )
cartridge_check.setObjectName("checkBox")
la.addWidget(QtGui.QLabel("Cartridge"))
cartridge_check.setChecked(False)
la.addWidget(cartridge_check)

onedrawing_check = QtGui.QCheckBox( DialogBox )
onedrawing_check.setObjectName("checkBox")
la.addWidget(QtGui.QLabel("Group drawings in page"))
cartridge_check.setChecked(False)
la.addWidget(onedrawing_check)

box = QtGui.QDialogButtonBox(DialogBox)

box = QtGui.QDialogButtonBox(DialogBox)
box.setOrientation(QtCore.Qt.Horizontal)
box.setStandardButtons(QtGui.QDialogButtonBox.Cancel|QtGui.QDialogButtonBox.Ok)
la.addWidget(box)

QtCore.QObject.connect(box, QtCore.SIGNAL("accepted()"), proceed )
QtCore.QObject.connect(box, QtCore.SIGNAL("rejected()"), close )
QtCore.QMetaObject.connectSlotsByName(DialogBox)
DialogBox.show()