Macro Unroll Ruled Surface

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



Installation
Copy the code file of the macro in the directory :
 * Linux & Mac  : $home/.Freecad/Mod/UnrollRuledSurface.
 * Windows : C:\Program Files\FreeCAD0.13

Add templates : A3_Landscape_Empty.svg A3_Landscape.svg  A4_Landscape_Empty.svg  A4_Landscape.svg

Cf Macro for unrolling ruled surfaces

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

 * 1) Select ruled surfaces
 * 2) Explode them (cf Draft menu)
 * 3) Select the surfaces
 * 4) Execute the macro

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

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


 * 1) Functions
 * 1) Functions
 * 1) Functions

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

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

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")

def close: DialogBox.hide
 * 1) Function close
 * 1) Function close

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")
 * 1) Class unrollRuledSurface
 * 2)     - file_name : ouput file
 * 3)     - pts_nbr : nbr point of
 * 4)       discretization
 * 1)       discretization

##################################### # 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.add( 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: ##################################### # 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"
 * 1) Class Drawing2d
 * 1) Class Drawing2d

##################################### # 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 done ##################################### 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

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

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

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 ] )
 * 1) Input fields
 * 1) Input fields

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