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 dircetory :
 * Linux  : $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
Select ruled surfaces

Explode them (cf Draft menu)

Select the surfaces

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 + 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 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

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