Macro Unroll Ruled Surface/ro: Difference between revisions

From FreeCAD Documentation
mNo edit summary
(Updating to match new version of source page)
 
(6 intermediate revisions by the same user not shown)
Line 1: Line 1:
<languages/>
<languages/>

<div class="mw-translate-fuzzy">
{{Macro/ro
{{Macro/ro
|Name=Macro Unroll Ruled Surface
|Name=Macro Unroll Ruled Surface
Line 8: Line 10:
|Date=2013-09-14
|Date=2013-09-14
}}
}}
</div>


== Description ==

<div class="mw-translate-fuzzy">
== Descriere ==
== Descriere ==
Acest macro permite să desfășurațu o suprafață rigaltă și să o desenați pe o foaie/pagină plană prestabilită: A3, A4.
Acest macro permite să desfășurațu o suprafață rigaltă și să o desenați pe o foaie/pagină plană prestabilită: A3, A4.
</div>


[[File:Macro_unrollRuledSurface_00.png|480px]]
<div class="mw-translate-fuzzy">
[[File:Macro_unrollRuledSurface_00.png|480px]]
[[File:Macro_unrollRuledSurface_00.png|480px]]
{{Caption|Macro_unrollRuledSurface}}
{{Caption|Macro_unrollRuledSurface}}
</div>

== Installation ==


<div class="mw-translate-fuzzy">
==Instalare==
==Instalare==
Copiați fișierul de cod al macrocomenzii în directorul:
Copiați fișierul de cod al macrocomenzii în directorul:
Line 21: Line 34:
Adăugați șabloane : A3_Landscape_Empty.svg A3_Landscape.svg A4_Landscape_Empty.svg A4_Landscape.svg<br />
Adăugați șabloane : A3_Landscape_Empty.svg A3_Landscape.svg A4_Landscape_Empty.svg A4_Landscape.svg<br />
Cf [http://forum.freecadweb.org/viewtopic.php?f=17&t=4563&p=35737#p35737 Macro for unrolling ruled surfaces]
Cf [http://forum.freecadweb.org/viewtopic.php?f=17&t=4563&p=35737#p35737 Macro for unrolling ruled surfaces]
</div>


See also: [http://forum.freecadweb.org/viewtopic.php?f=17&t=4563&p=35737#p35737 Macro for unrolling ruled surfaces].

== Options ==

<div class="mw-translate-fuzzy">
== Opțiuni ==
== Opțiuni ==
* Numărul de generatrix
* Numărul de generatrix
Line 27: Line 46:
* Formatul paginii: a3/a4, cartuș (cf. șabloane FreeCAD)
* Formatul paginii: a3/a4, cartuș (cf. șabloane FreeCAD)
* Desene de grup în aceeași pagină posibil.
* Desene de grup în aceeași pagină posibil.
</div>


[[File:Macro_UnrollRuledSurface_start_form.png|Macro_unrollRuledSurface]]
[[File:Macro unrollRuledSurface 01.png]]
{{Caption|Macro_unrollRuledSurface}}


== Usage ==

<div class="mw-translate-fuzzy">
==Instrucțiuni de utilizare==
==Instrucțiuni de utilizare==
# Selectați suprafețele conduse
# Selectați suprafețele conduse
Line 36: Line 58:
# Selectați suprafețele
# Selectați suprafețele
# Executați macroul
# Executați macroul
</div>


==Script==
==Script==


The latest version of the macro is here on the wiki. An earlier version can be found at [https://github.com/FreeCAD/FreeCAD-macros/blob/master/Drawing/UnrollRuledSurface.FCMacro UnrollRuledSurface.FCMacro], but the easiest way to install this macro is through the [[File:Std_AddonMgr.svg|24px|Std_AddonMgr]] [[Std_AddonMgr|Addon manager]].
'''Macro_unrollRuledSurface.py'''


ToolBar Icon [[Image:Macro_Unroll_Ruled_Surface.png]]
{{Code|code=


'''Macro_unrollRuledSurface.py'''

<!--DO NOT CHANGE THE <pre> </pre> TAG, code contains pipe or space character which breaks the wiki macro-->
<pre>
# -*- coding: utf-8 -*-
#***************************************************************************
#***************************************************************************
#* *
#* *
#* Copyright (c) 2013 - DoNovae/Herve BAILLY <hbl13@donovae.com> *
#* Copyright (c) 2013 - DoNovae/Herve BAILLY <hbl13@donovae.com> *
#* Copyright (c) 2022 - heda <heda@fc-forum> *
#* *
#* *
#* This program is free software; you can redistribute it and/or modify *
#* This program is free software; you can redistribute it and/or modify *
Line 64: Line 93:
#* *
#* *
#***************************************************************************
#***************************************************************************
__Name__ = 'Unroll Ruled Surface'
__Comment__ = 'Unroll of a ruled surface and draw it on a page.'
__Author__ = 'Hervé B., heda'
__Version__ = '1.1'
__Date__ = '2022-07-24'
__License__ = 'LGPL-2.0-or-later'
__Web__ = 'https://wiki.freecad.org/Macro_Unroll_Ruled_Surface'
__Wiki__ = 'https://wiki.freecad.org/Macro_Unroll_Ruled_Surface'
__Icon__ = ''
__Help__ = ('Select ruled surfaces, Explode them (cf Draft menu), '
'Select the surfaces, Execute the macro')
__Status__ = ''
__Requires__ = ''
__Communication__ = ''
__Files__ = ''


__doc__ = """
#####################################
select a face, or several and run the macro.
# Macro UnrollRuledSurface
a shell/solid needs to be draft/downgraded to get the faces as separate objects
# Unroll of a ruled surface
#####################################
import FreeCAD , FreeCADGui , Part, Draft, math, Drawing , PySide, os
from PySide import QtGui,QtCore
from FreeCAD import Base
#from unrollRuledSurface.unfoldBox import unfoldBoxClass # commented 06/08/2015 give error "No module named unrollRuledSurface.unfoldBox"
fields_l = []
unroll_l = []


the macro is intended to unroll lofted faces,
function beyond that is (in current version) a bonus

settings are not context aware, all settings not applicable are ignored.
as example, using autoscaling ignores the scale-value in the form


v1.1 (2022-07-24) py3/qt5 compat, cosmetic code changes, minor code tweaks,
used gridlayout for form, added option to skip drawing,
made new layout engine with techdraw,
the drawing wb layout engine is now called "legacy"
v1.0.1 (2019-02-01) - on git
v1.0 (2013-09-14) - on wiki

note:
- unfolding sometimes works and sometimes not
- not all surfaces are theoretically possible to unroll

"""

import os
from PySide import QtGui, QtCore
import FreeCAD, FreeCADGui
import Part, Draft

Vector = FreeCAD.Base.Vector
PrintMessage = FreeCAD.Console.PrintMessage
PrintError = FreeCAD.Console.PrintError

settings = dict()
unroll_l = []
dwgtpllegacy = 'Mod/Drawing/Templates'
dwgtpl = 'Mod/TechDraw/Templates'


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


#####################################
#####################################
### Functions
# Function errorDialog
#####################################
#####################################

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

def ending():
PrintMessage("UnrollRuledSurface: end.\n")
PrintMessage("===========================================\n")
FreeCAD.ActiveDocument.recompute()




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


FreeCAD.Console.PrintMessage("===========================================\n")
PrintMessage("===========================================\n")
FreeCAD.Console.PrintMessage("UnrollRuledSurface: start.\n")
PrintMessage("UnrollRuledSurface: start.\n")
try:
try:
file_name = fields_l[0].text()
sts = lambda s: settings.get(s)
pts_nbr = float(fields_l[1].text())
scale = float(fields_l[2].text())
scale_auto = scale_check.isChecked()
edge0 = edge0_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.edge0_check: "+str(edge0)+"")
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)


file_name = sts("fname").text()
QtGui.qApp.restoreOverrideCursor()
pts_nbr = float(sts("dpts").text())
DialogBox.hide()
makedwg = sts("mkdwg").isChecked()
unrollRS=unrollRuledSurface( file_name , pts_nbr , edge0 )
leglay = sts("legacylayout").isChecked()
#
scale = float(sts("scale").text()) # ignored if autoscale is set
# Get selection
scale_auto = sts("autoscale").isChecked()
#
edge0 = sts("edge").checkedId() == -2
sel=FreeCADGui.Selection.getSelection()
a3 = sts("papersize").checkedId() == -3
faceid=0
cartridge = sts("cartridge").isChecked()
objnames_l=[]
onedrawing = sts("groupdwg").isChecked()
objnames0_l=[]

grp=FreeCAD.activeDocument().addObject("App::DocumentObjectGroup", str(file_name)+"_objs")
PrintMessage("UnrollRuledSurface.file_name: {}\n".format(file_name))
for objid in range( sel.__len__() ):
PrintMessage("UnrollRuledSurface.pts_nbr: {}\n".format(pts_nbr))
shape=sel[objid].Shape
PrintMessage("UnrollRuledSurface.edge0: {}\n".format(edge0))
faces=shape.Faces
PrintMessage("UnrollRuledSurface.makedwg: {}\n".format(makedwg))
for id in range( faces.__len__() ):
FreeCAD.Console.PrintMessage("UnrollRuledSurface.proceed: ObjId= "+str(objid)+" , faceId= "+str( faceid )+"\n")
PrintMessage("UnrollRuledSurface.leglay: {}\n".format(leglay))
PrintMessage("UnrollRuledSurface.scale_check: {}\n".format(scale_auto))
if faces.__len__() > 1:
PrintMessage("UnrollRuledSurface.scale: {}\n".format(scale))
name=sel[objid].Name+".faces "+str(id)
PrintMessage("UnrollRuledSurface.a3_check: {}\n".format(a3))
else:
PrintMessage("UnrollRuledSurface.cartridge: {}\n".format(cartridge))
name=sel[objid].Name
PrintMessage("UnrollRuledSurface.onedrawing: {}\n".format(onedrawing))
obj=unrollRS.unroll(faces[id],name)
except:
obj.ViewObject.Visibility=False
msg = "UnrollRuledSurface: wrong inputs...\n"
grp.addObject(obj)
PrintError(msg)
objnames_l.append( [ obj , name ] )
errorDialog(msg)
objnames0_l.append( [ sel[objid] , name ] )
QtGui.QApplication.restoreOverrideCursor()
faceid=faceid+1
DialogBox.hide()
id=0
ending()
while objnames_l.__len__() > 0:
return
draw=Drawing2d( scale, scale_auto , a3 , cartridge , onedrawing,str(file_name)+"_page"+str(id) )

objnames_l=draw.all( objnames_l )
QtGui.QApplication.restoreOverrideCursor()
id=id+1
DialogBox.hide()
FreeCAD.Console.PrintMessage("UnrollRuledSurface: obj_l= "+str(objnames_l.__len__())+"\n")
unrollRS = unrollRuledSurface(file_name, pts_nbr, edge0)

## Get selection
sel = FreeCADGui.Selection.getSelection()
if not sel:
PrintMessage("UnrollRuledSurface: no selection...\n")
ending()
return

faceid = 0
objnames_l, objnames0_l = [], []
grp = FreeCAD.ActiveDocument.addObject("App::DocumentObjectGroup",
"{}_objs".format(file_name))

for objid, obji in enumerate(sel):
shape = obji.Shape
faces = shape.Faces
for idx in range(len(faces)):
msg = "UnrollRuledSurface.proceed: ObjId = {}, faceId = {}\n"
PrintMessage(msg.format(objid, faceid))
name = obji.Name
if len(faces) > 1:
name = "{}.face_{}".format(name, idx)
obj = unrollRS.unroll(faces[idx], name)
obj.ViewObject.Visibility = not makedwg
grp.addObject(obj)

objnames_l.append([obj, name])
objnames0_l.append([obji, name])
faceid += 1

if not makedwg:
ending()
return

if leglay:
idx = 0
while len(objnames_l) > 0:
draw = Drawing2dLegacy(scale, scale_auto, a3,
cartridge, onedrawing,
"{}_page{:02}".format(file_name, idx))
objnames_l = draw.all2d(objnames_l)
idx += 1
msg = "UnrollRuledSurface: obj_l = {}\n"
PrintMessage(msg.format(len(objnames_l)))

else:
draw = Drawing2d(scale, scale_auto, a3, cartridge)
n = 4 if onedrawing else 1
chunks = [objnames_l[i:i + n] for i in range(0, len(objnames_l), n)]
for i, chunk in enumerate(chunks, start=1):
draw.drawpage(chunk, "{}_page{:02}".format(file_name, i))

ending()


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


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

def getType(obj):
return type(obj).__name__



#####################################
# Class unrollRuledSurface
# - file_name : ouput file
# - pts_nbr : nbr point of
# discretization
#####################################
class unrollRuledSurface:
class unrollRuledSurface:
"""
def __init__( self, file_name, pts_nbr , edge0 ):
unroll ruled surfaces
self.doc = FreeCAD.activeDocument()
self.file_name = file_name
:file_name: ouput file
self.pts_nbr = int(pts_nbr)
:pts_nbr: nbr point of discretization
"""
self.edge0 = edge0
FreeCAD.Console.PrintMessage("UnrollRuledSurface.unroll - file_name: "+self.file_name+" , pts_nbr: "+str(self.pts_nbr)+"\n")
def __init__(self, file_name, pts_nbr, edge0):
self.doc = FreeCAD.ActiveDocument
self.file_name = file_name
self.pts_nbr = int(pts_nbr)
self.edge0 = edge0
msg = "UnrollRuledSurface.unroll - file_name: {}, pts_nbr: {}\n"
PrintMessage(msg.format(file_name, pts_nbr))




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


def nbpoles(self, curve):
#####################################
"""find number of poles for a curve"""
# Function nbpoles
if getType(curve) == 'GeomLineSegment':
#####################################
nbpol=1
def nbpoles(self,curve):
if type(curve).__name__=='GeomLineSegment':
elif getType(curve) == 'GeomBSplineCurve':
nbpol=1
nbpol=curve.NbPoles
elif type(curve).__name__=='GeomBSplineCurve':
elif getType(curve) == 'GeomCircle':
nbpol=curve.NbPoles
nbpol=2
elif type(curve).__name__=='GeomCircle':
elif getType(curve) == 'GeomBezierCurve':
nbpol=2
nbpol=4
else:
elif type(curve).__name__=='GeomBezierCurve':
nbpol=4
nbpol=0
else:
nbpol=0
FreeCAD.Console.PrintMessage("UnrollRulrdSurface.nbpole {:s} = {:d}\n".format(type(curve).__name__,nbpol))
return nbpol


msg = "UnrollRulrdSurface.nbpole {:s} = {:d}\n"
#####################################
PrintMessage(msg.format(getType(curve), nbpol))
# Function unroll
return nbpol
#####################################
# Unroll of a face
# composed of 2 or 4 edges
#####################################
def unroll(self,face,name):
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)
FreeCAD.Console.PrintMessage("UnrollRuledSurface.unroll: nbpol0= {:d}, nbpol1= {:d}, nbpol2= {:d}, nbpol3= {:d}\n".format(nbpol0,nbpol1,nbpol2,nbpol3))


def unroll(self, face, name):
if self.edge0:
"""unrolls a face composed of 2 to 4 edges"""
e1=E0
e2=E2
nbredges = len(face.Edges)
v=self.discretize( E1 )
msg = "UnrollRuledSurface.unroll: Edge Nbr = {}\n"
PrintMessage(msg.format(nbredges))
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]


if nbredges == 2:
sd1=self.discretize( e1 )
sd2=self.discretize( e2 )
e1, e2 = face.Edges
sd1 = e1.Curve.discretize(self.pts_nbr)
#
sd2 = e2.Curve.discretize(self.pts_nbr)
# Reverse if curves cross over
#
if not ( sd2[0].__eq__( v0 ) or not sd2[0].__eq__( v1 ) ):
sd2.reverse()


elif nbredges == 3:
#
e1, _, e2 = face.Edges
# Create a polygon object and set its nodes
sd1 = e1.Curve.discretize(self.pts_nbr)
#
sd2 = e2.Curve.discretize(self.pts_nbr)
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",name)
p.Nodes=devlxy_l
self.doc.recompute()
FreeCADGui.SendMsgToActiveView("ViewFit")
return p


else:
#####################################
E0, E1, E2, E3, *_ = face.Edges
# Function vect_copy
## Choose more complexe curve as edge
# - vect:
nbpol0 = self.nbpoles(E0.Curve)
# - return copy of vector
nbpol1 = self.nbpoles(E1.Curve)
#####################################
nbpol2 = self.nbpoles(E2.Curve)
def vect_copy( self, vect):
nbpol3 = self.nbpoles(E3.Curve)
v= vect.add( FreeCAD.Base.Vector(0,0,0) )
msg = ("UnrollRuledSurface.unroll: nbpol0= {:d}, nbpol1= {:d},"
return v
" nbpol2= {:d}, nbpol3= {:d}\n")
PrintMessage(msg.format(nbpol0, nbpol1, nbpol2, nbpol3))


if self.edge0:
#####################################
e1, e2 = E0, E2
# Function vect_cos
v = self.discretize(E1)
# - vect1,2:
v0, v1 = v[0], v[self.pts_nbr-1]
# - return cos angle between
# 2 vectors
else:
e1, e2 = E1, E3
#####################################
v = self.discretize(E2)
def vect_cos( self , vect1, vect2 ):
v0, v1 = v[0], v[self.pts_nbr-1]
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


sd1 = self.discretize(e1)
sd2 = self.discretize(e2)
#####################################
## Reverse if curves cross over
# Function devlxyz
if not (sd2[0].__eq__(v0) or not sd2[0].__eq__(v1)):
# - vect1,2: 2 edges of the shape
# - return dvlxy_l
sd2.reverse()
#####################################
# 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)


## Create a polygon object and set its nodes
devlxy_l=[]
devlxy_l = self.devlxyz(sd1, sd2)
devl1xy_l=[]
msg = "UnrollRuledSurface.unroll: size devlxy_l: {}\n"
devl2xy_l=[]
PrintMessage(msg.format(len(devlxy_l)))
errormax=0.0
p = self.doc.addObject("Part::Polygon", name)
#
p.Nodes = devlxy_l
# Init unroll
self.doc.recompute()
# AB
FreeCADGui.ActiveDocument.ActiveView.fitAll()
#
return p
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
#self.draw_line( devl1xy_l[0] , devl2xy_l[0] )
#self.draw_line( vect1[0] , vect2[0] )
for j in range( 1 , self.pts_nbr ) :
#
# AB
#
ab=vect2[j-1].sub(vect1[j-1])
#self.draw_line( vect1[j-1] , vect2[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)
#FreeCAD.Console.PrintMessage("UnrollRuledSurface.alp a1b1: "+str(a1b1n.getAngle(a1b1on))+"\n")
#FreeCAD.Console.PrintMessage("UnrollRuledSurface.alp oc1: "+str(a1b1n.getAngle(a1c1)-alp)+"\n")
#FreeCAD.Console.PrintMessage("UnrollRuledSurface.length oc1: "+str(a1c1.Length-ac.Length)+"\n")
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)
#FreeCAD.Console.PrintMessage("UnrollRuledSurface.alp od1: "+str(b1a1n.getAngle(b1d1)-alp)+"\n")
#FreeCAD.Console.PrintMessage("UnrollRuledSurface.length od1: "+str(b1d1.Length-bd.Length)+"\n")
ob1=self.vect_copy(devl2xy_l[j-1])
od1=ob1.add(b1d1)
devl2xy_l.append(od1)
#
# Draw generatrice
#
#self.draw_line( devl1xy_l[j] , devl2xy_l[j] )
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))


def vect_copy(self, vect):
#
"""returns copy of vector"""
# Close polygone
return vect.add(Vector())
#
devlxy_l = devl1xy_l
devl2xy_l.reverse()
devlxy_l.extend( devl2xy_l )
v=FreeCAD.Vector(0,0,0)
devlxy_l.append( v )


def vect_cos(self, vect1, vect2):
return devlxy_l
"""returns cosine angle between 2 vectors"""
return vect1.dot(vect2) / vect1.Length / vect2.Length


def vect_sin(self, vect1, vect2):
"""returns absolute sinus angle between 2 vectors"""
v = Vector()
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
return v.Length / vect1.Length / vect2.Length


def devlxyz(self, vect1, vect2):
#####################################
"""
# Function draw_line
unrolls a face composed of 4 edges
# - vect0,1: two points
args: vect1, vect2 --> 2 edges of the shape
#####################################
returns: dvlxy
def draw_line( self , vect0 , vect1 ):
l=Part.Line()
"""
lenv1, lenv2 = len(vect1), len(vect2)
l.StartPoint=vect0
if lenv1 != lenv2 or lenv1 != self.pts_nbr or lenv2 != self.pts_nbr:
l.EndPoint=vect1
msg = ("UnrollRuledSurface.devlxyz: incompatility of sizes vect1,"
self.doc.addObject("Part::Feature","Line").Shape=l.toShape()
" vect2, pts_nbr: ({}, {}, {})\n")
PrintError(msg.format(lenv1, lenv2, self.pts_nbr))
errorDialog(msg)

devlxy_l, devl1xy_l, devl2xy_l = [], [], []
errormax = 0.0
## Init unroll
## AB
a1b1 = vect2[0].sub(vect1[0])
oa1 = Vector(0, 0, 0)
devl1xy_l.append(oa1) #A1
ob1 = Vector(a1b1.Length, 0, 0)
devl2xy_l.append(ob1) #B1
#self.draw_line(devl1xy_l[0], devl2xy_l[0])
#self.draw_line(vect1[0], vect2[0])
for j in range(1, self.pts_nbr):

## AB
ab = vect2[j-1].sub(vect1[j-1])
#self.draw_line(vect1[j-1], vect2[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 = 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
#self.draw_line(devl1xy_l[j], devl2xy_l[j])
c1d1 = devl2xy_l[j].sub(devl1xy_l[j])
if ab.Length != 0:
abl = ab.Length
errormax = max(errormax, abs(abl - c1d1.Length) / abl)

msg = "UnrollRuledSurface Error cd,c1d1: {:.1f} %\n"
PrintMessage(msg.format(errormax*100))

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

return devlxy_l


def draw_line(self, vect0, vect1):
"""draws a Part.Line between vect0 & vect1"""
l = Part.LineSegment()
l.StartPoint = vect0
l.EndPoint = vect1
self.doc.addObject("Part::Feature", "Line").Shape = l.toShape()


class Scale:
"""keeps autoscaling to integers"""
def __init__(self, scale):
self.scale = scale if scale >= 1 else 1 / scale
self.scale = int(self.scale)
self.inverted = scale >= 1

def get(self):
if self.inverted:
return self.scale, '{}:1'.format(self.scale)
else:
return 1/self.scale, '1:{}'.format(self.scale)




#####################################
# Class Drawing2d
# -obj_l: listes of object
# -topxh1
# -topyh1
# -topxv1
# -topyv1
# -topxvmax1
# -topyvmax1
# -topxvmax1
# -topyvmax1
#####################################
class Drawing2d:
class Drawing2d:
"""
#####################################
TechDraw wb layout engine, diffeent logic compared to drawing wb layout.
# Function __init__
# - Scale
serves basic purpose...
"""
# - scale_auto
def __init__(self, scale, scale_auto, a3, cartridge):
# - a3
# - cartridge
# - onedrawing
#####################################
def __init__( self, scale , scale_auto , a3 , cartridge , onedrawing , page_str ):
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_str


self.a3 = a3
#####################################
self.scale = scale
# Function newPage
self.scale_auto = scale_auto
#####################################
def newPage( self ):
self.cartridge = cartridge
if a3:
freecad_dir=os.getenv('HOME')+"/.FreeCAD/Mod/unrollRuledSurface"
self.WH = 420, 297
page = FreeCAD.activeDocument().addObject('Drawing::FeaturePage', self.name )
if self.a3:
if self.cartridge:
page.Template = freecad_dir+'/A3_Landscape.svg'
else:
else:
page.Template = freecad_dir+'/A3_Landscape_Empty.svg'
self.WH = 297, 210
self.doc = FreeCAD.ActiveDocument
else:

if self.cartridge:

page.Template = freecad_dir+'/A4_Landscape.svg'
def _mkquadrants(self, nbr):
"""centers of quadrants w/o margin"""
w, h = self.WH
w2, h2 = w/2, h/2
w4, h4 = w/4, h/4

q = {1: [[w, h], [[w2, h2]]],
2: [[w2, h], [[w2 - w4, h2], [w2 + w4, h2]]],
3: [[w2, h2], [[w2 - w4, h2 + h4], [w2 + w4, h2 + h4],
[w2 - w4, h2 - h4]]],
4: [[w2, h2], [[w2 - w4, h2 + h4], [w2 + w4, h2 + h4],
[w2 - w4, h2 - h4], [w2 + w4, h2 - h4]]]}
return q.get(nbr)


def newPage(self, doc, page_name):
freecad_dir = os.path.join(FreeCAD.getResourceDir(), dwgtpl)
page = doc.addObject('TechDraw::DrawPage', page_name)
template = self.doc.addObject('TechDraw::DrawSVGTemplate', 'Template')
size = 'A3' if self.a3 else 'A4'
frame = 'TD' if self.cartridge else '_blank'
template.Template = freecad_dir + '/{}_Landscape{}.svg'.format(size, frame)
page.Template = template
return page

def drawpage(self, faces, page_name):
"""max 4 per page, simple layout with halfs or quadrants"""
page = self.newPage(self.doc, page_name)
[W, H], ll = self._mkquadrants(len(faces))
for [face, name], [x0, y0] in zip(faces, ll):
bb = face.Shape.BoundBox
xr, yr = W / bb.XLength, H / bb.YLength
adjust = 0.7 if self.cartridge else 0.85
scale = min(xr, yr) * adjust if self.scale_auto else self.scale
scale, scr = Scale(scale).get()
bb.scale(scale, scale, 1) # unrolled faces in xy-plane

TopView = self.doc.addObject('TechDraw::DrawViewPart', name + ' view')
page.addView(TopView)
TopView.Source = face
TopView.Direction = (0, 0, 1)
TopView.XDirection = (1, 0, 0)
TopView.Scale = scale
TopView.X = x0
TopView.Y = y0

Text = self.doc.addObject('TechDraw::DrawViewAnnotation', name + ' txt')
page.addView(Text)
Text.Text = '{} [{}]'.format(name, scr)
Text.recompute() # for size
Text.X = x0
yt = y0 - bb.YLength / 2 - Text.TextSize.Value * 1.5
Text.Y = max(1, yt)

self.doc.recompute()
page.ViewObject.doubleClicked()
FreeCADGui.runCommand('TechDraw_ToggleFrame', 0)


class Drawing2dLegacy:
"""
makes 2d drawing with Drawing wb (original layout engine, now legacy)
- obj_l: list of objects
"""
## untouched logic in v1.1
def __init__(self, scale, scale_auto, a3, cartridge, onedrawing, page_str):
self.TopX_H = self.TopY_H = 0
self.TopX_V = self.TopY_V = 0
self.TopX_Hmax = self.TopY_Hmax = 0
self.TopX_Vmax = self.TopY_Vmax = 0
self.a3 = a3
self.scale = scale
self.scale_auto = scale_auto
self.cartridge = cartridge
self.onedrawing = onedrawing
self.marge = 6
if self.a3:
self.L, self.H = 420, 297
else:
else:
page.Template = freecad_dir+'/A4_Landscape_Empty.svg'
self.L, self.H = 297, 210
self.name = page_str
return page


def newPage(self):
freecad_dir = os.path.join(FreeCAD.getResourceDir(), dwgtpllegacy)
doc = FreeCAD.ActiveDocument
page = doc.addObject('Drawing::FeaturePage', self.name)
size = 'A3' if self.a3 else 'A4'
frame = '' if self.cartridge else '_plain'
page.Template = freecad_dir + '/{}_Landscape{}.svg'.format(size, frame)
return page


#####################################
# Function all
#####################################
def all( self, objname_l ):
obj1_l=[]
for objid in range( objname_l.__len__() ):
if objid == 0 or not self.onedrawing:
page = self.newPage()
obj1_l.extend( self.done( objid , objname_l[objid] ))
return obj1_l


def all2d(self, objname_l):
#####################################
obj1_l = []
# Function all
for objid in range(len(objname_l)):
#####################################
if objid == 0 or not self.onedrawing:
def done( self, id , objname ):
page = self.newPage()
#
obj1_l.extend(self.done(objid, objname_l[objid]))
# Init
return obj1_l
#
obj_l=[]
obj=objname[0]
objname=objname[1]
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


def done(self, idx, objname):
scale=min((self.L-4*self.marge)/xmax,(self.H-4*self.marge)/ymax)
obj_l = []
obj, objname = objname
marge = self.marge
bb = obj.Shape.BoundBox
xmax = bb.XMax - bb.XMin
ymax = bb.YMax - bb.YMin


if ( not self.scale_auto ) or ( self.onedrawing ) :
if ymax > xmax:
scale=self.scale
Draft.rotate(obj, 90)
Draft.move(obj, Vector(-bb.XMin, -bb.YMin, 0))
xmax = bb.XMax - bb.XMin
ymax = bb.YMax - bb.YMin


scale = min((self.L-4 * marge) / xmax, (self.H-4 * marge) / ymax)
FreeCAD.Console.PrintMessage("UnrollRuledSurface.drawing: scale= {:.2f}\n".format(scale))


if id == 0 or not self.onedrawing:
if (not self.scale_auto) or self.onedrawing:
scale = self.scale
#
# Init
#
FreeCAD.Console.PrintMessage("Dawing2d: init\n")
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
#
FreeCAD.Console.PrintMessage("Dawing2d: horizontal\n")
TopX=self.TopX_H
TopY=self.TopY_H
self.TopX_H=self.TopX_H + xmax * scale + self.marge
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
#
FreeCAD.Console.PrintMessage("Dawing2d: vertival\n")
if self.TopX_V + ymax * scale +2* self.marge < self.L and self.TopY_V + xmax * scale + 2*self.marge < self.H :
Draft.rotate( obj , 90 )
Draft.move( obj , FreeCAD.Base.Vector( -obj.Shape.BoundBox.XMin , -obj.Shape.BoundBox.YMin , 0))
x0=xmax;xmax=ymax,ymax=x0
self.TopX_V=max(self.TopX_Vmax, self.TopX_V)
TopX=self.TopX_V
TopY=self.TopY_V
self.TopX_V = self.TopX_V + xmax * scale + self.marge
self.TopY_Vmax=max( self.TopY_Vmax , self.TopY_V + ymax * scale + self.marge )
else:
obj_l.append( [ obj , name ] )
return obj_l


PrintMessage("UnrollRuledSurface.drawing: scale= {:.2f}\n".format(scale))
else:
#
# H Carriage return
#
if ( self.TopY_Hmax + ymax * scale + self.marge*2 < self.H ):
FreeCAD.Console.PrintMessage("Dawing2d: carriage return: "+str(self.TopY_H + ymax * scale )+" > "+str(self.H)+"\n")
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_Hmax + 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
#
FreeCAD.Console.PrintMessage("Dawing2d: vertival: "+str(self.TopX_V)+" , "+str(self.TopX_Vmax)+"\n")
if self.TopX_V + ymax * scale + 2*self.marge < self.L and self.TopY_V + xmax * scale + 2*self.marge < self.H :
Draft.rotate( obj , 90 )
Draft.move( obj , FreeCAD.Base.Vector( -obj.Shape.BoundBox.XMin , -obj.Shape.BoundBox.YMin , 0))
x0=xmax;xmax=ymax,ymax=x0
TopX=self.TopX_V
TopY=self.TopY_V
self.TopX_V = self.TopX_V + xmax * scale + self.marge
self.TopY_Vmax=max( self.TopY_Vmax , self.TopY_V + ymax * scale + self.marge )
else:
obj_l.append( [ obj , objname ] )
return obj_l


page=FreeCAD.activeDocument().getObject(self.name )


if idx == 0 or not self.onedrawing:
Text=FreeCAD.activeDocument().addObject('Drawing::FeatureViewAnnotation', objname+"_txt")
PrintMessage("Drawing2d: init\n")
Text.Text=objname
TopX = self.TopX_H = marge * 2
Text.X=TopX+xmax*scale/2
TopY = self.TopY_H = marge * 2
Text.Y=TopY+ymax*scale/2
self.TopX_H = self.TopX_H + xmax * scale + marge
Text.Scale=2
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 + marge)
self.TopX_Vmax = max(self.TopX_Vmax, self.TopX_Hmax)
self.TopX_V = max(self.TopX_Vmax, self.TopX_V)
self.TopY_V = marge * 2


elif self.onedrawing:
TopView = FreeCAD.activeDocument().addObject('Drawing::FeatureViewPart',objname)
if self.TopX_H + xmax * scale < self.L:
TopView.Source = obj
if self.TopY_H + ymax * scale + marge * 2 < self.H:
TopView.Direction = (0.0,0.0,1)
## H Add at right on same horizontal line
TopView.Rotation = 0
PrintMessage("Drawing2d: horizontal\n")
TopView.X = TopX
TopX, TopY = self.TopX_H, self.TopY_H
TopView.Y = TopY
self.TopX_H = self.TopX_H + xmax * scale + marge
TopView.ShowHiddenLines = False
self.TopX_Hmax = max(self.TopX_Hmax, self.TopX_H)
TopView.Scale = scale
self.TopY_Hmax = max(self.TopY_Hmax,
page.addObject(TopView)
self.TopY_H + ymax * scale + marge)
page.addObject(Text)
self.TopX_Vmax = max(self.TopX_Hmax, self.TopX_Vmax)
FreeCAD.activeDocument().recompute()
self.TopX_Vmax = max(self.TopX_Vmax, self.TopX_Hmax)
return obj_l
self.TopX_V = max(self.TopX_Vmax, self.TopX_V)


else:
## V Add at right on same horizontal line
PrintMessage("Drawing2d: vertival\n")
if (self.TopX_V + ymax * scale + 2 * marge < self.L
and self.TopY_V + xmax * scale + 2 * marge < self.H):
Draft.rotate(obj, 90)
Draft.move(obj, Vector(-bb.XMin, -bb.YMin, 0))
x0 = xmax; xmax = ymax; ymax = x0
self.TopX_V = max(self.TopX_Vmax, self.TopX_V)
TopX, TopY = self.TopX_V, self.TopY_V
self.TopX_V = self.TopX_V + xmax * scale + marge
self.TopY_Vmax = max(self.TopY_Vmax,
self.TopY_V + ymax * scale + marge)

else:
obj_l.append([obj, self.name])
return obj_l

else:
## H Carriage return
if self.TopY_Hmax + ymax * scale + self.marge*2 < self.H:
msg = "Drawing2d: carriage return: {} > {}\n"
PrintMessage(msg.format(self.TopY_H + ymax * scale, 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_Hmax + 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
msg = "Drawing2d: vertival: {} , {}\n"
PrintMessage(msg.format(self.TopX_V, self.TopX_Vmax))
if (self.TopX_V + ymax * scale + 2 * marge < self.L
and self.TopY_V + xmax * scale + 2 * marge < self.H):
Draft.rotate(obj, 90)
Draft.move(obj, Vector(-bb.XMin, -bb.YMin, 0))
x0 = xmax; xmax = ymax; ymax = x0
TopX, TopY = self.TopX_V, self.TopY_V
self.TopX_V = self.TopX_V + xmax * scale + marge
self.TopY_Vmax = max(self.TopY_Vmax,
self.TopY_V + ymax * scale + marge)
else:
obj_l.append([obj, objname])
return obj_l

doc = FreeCAD.ActiveDocument
page = doc.getObject(self.name)

Text = doc.addObject('Drawing::FeatureViewAnnotation', f"{objname}_txt")
Text.Text = objname
Text.X = TopX + xmax * scale / 2
Text.Y = TopY + ymax * scale / 2
Text.Scale = 2

TopView = doc.addObject('Drawing::FeatureViewPart', objname)
TopView.Source = obj
TopView.Direction = (0, 0, 1)
TopView.Rotation = 0
TopView.X = TopX
TopView.Y = TopY
TopView.ShowHiddenLines = False
TopView.Scale = scale
page.addObject(TopView)
page.addObject(Text)
doc.recompute()
page.ViewObject.doubleClicked()
return obj_l






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


DialogBox = QtGui.QDialog()
DialogBox = QtGui.QDialog()
DialogBox.resize(250,250)
DialogBox.setWindowTitle("UnrollRuledSurface")
DialogBox.setWindowTitle("UnrollRuledSurface")
la = QtGui.QVBoxLayout(DialogBox)
la = QtGui.QGridLayout(DialogBox)
la.setSpacing(7)
buttonGrp1 = QtGui.QButtonGroup(DialogBox)
buttonGrp2 = QtGui.QButtonGroup(DialogBox)
buttonGrpEdge = QtGui.QButtonGroup(DialogBox)
buttonGrpFormat = QtGui.QButtonGroup(DialogBox)


cols = 4
#
la.addWidget(QtGui.QLabel("File Name"), 0, 0, 1, cols)
# Input fields
fname = QtGui.QLineEdit("UnrollSurface")
#
la.addWidget(fname, 1, 0, 1, cols)
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 ] )


la.addWidget(QtGui.QLabel("Discretization Points Nbr"), 2, 0, 1, cols)
scale_check = QtGui.QCheckBox( DialogBox )
dpts = QtGui.QLineEdit("30")
scale_check.setObjectName("checkBox")
la.addWidget(dpts, 3, 0, 1, 2)
scale_check.setChecked(True)
la.addWidget(QtGui.QLabel("Scale auto"))
la.addWidget(scale_check)


###
line3 = QtGui.QFrame(DialogBox)
la.addWidget(QtGui.QLabel("Generatrices from edge:"), 4, 0, 1, 2)
line3.setFrameShape(QtGui.QFrame.HLine)
edgezero = QtGui.QRadioButton("0 to 3")
line3.setFrameShadow(QtGui.QFrame.Sunken)
la.addWidget(line3)
la.addWidget(edgezero, 4, 2)
edgeone = QtGui.QRadioButton("1 to 4")
la.addWidget(edgeone, 4, 3)
buttonGrpEdge.addButton(edgezero); buttonGrpEdge.addButton(edgeone)
edgezero.setChecked(True)


###
edge0_check = QtGui.QRadioButton( DialogBox )
mkdwg = QtGui.QCheckBox("Make drawing")
la.addWidget(QtGui.QLabel("Generatrices from edge 1 to 4" ))
edge0_check.setChecked(False)
mkdwg.setChecked(True)
la.addWidget(edge0_check)
la.addWidget(mkdwg, 5, 0, 1, 2)
edge1_check = QtGui.QRadioButton( DialogBox )
la.addWidget(QtGui.QLabel("Generatrices from edge 0 to 3" ))
edge1_check.setChecked(True)
buttonGrp1.addButton(edge0_check)
buttonGrp1.addButton(edge1_check)
la.addWidget(edge1_check)


line4 = QtGui.QFrame(DialogBox)
legacylayout = QtGui.QCheckBox("Legacy layout")
legacylayout.setChecked(False)
line4.setFrameShape(QtGui.QFrame.HLine)
la.addWidget(legacylayout, 5, 2, 1, 2)
line4.setFrameShadow(QtGui.QFrame.Sunken)
la.addWidget(line4)


a3_check = QtGui.QRadioButton( DialogBox )
la.addWidget(QtGui.QLabel("A3" ))
a3_check.setChecked(False)
la.addWidget(a3_check)
a4_check = QtGui.QRadioButton( DialogBox )
la.addWidget(QtGui.QLabel("A4"))
a4_check.setChecked(True)
buttonGrp2.addButton(a3_check)
buttonGrp2.addButton(a4_check)
la.addWidget(a4_check)


###
cartridge_check = QtGui.QCheckBox( DialogBox )
autoscale = QtGui.QCheckBox("Auto scale")
cartridge_check.setObjectName("checkBox")
autoscale.setChecked(True)
la.addWidget(QtGui.QLabel("Cartridge"))
la.addWidget(autoscale, 6, 0, 1, 2)
cartridge_check.setChecked(False)
la.addWidget(cartridge_check)


la.addWidget(QtGui.QLabel("Scale"), 6, 2)
line6 = QtGui.QFrame(DialogBox)
scale = QtGui.QLineEdit("1")
line6.setFrameShape(QtGui.QFrame.HLine)
la.addWidget(scale, 6, 3)
line6.setFrameShadow(QtGui.QFrame.Sunken)
la.addWidget(line6)


###
onedrawing_check = QtGui.QCheckBox( DialogBox )
la.addWidget(QtGui.QLabel("Paper size:"), 7, 0, 1, 2)
onedrawing_check.setObjectName("checkBox")
rba4 = QtGui.QRadioButton("A4")
la.addWidget(QtGui.QLabel("Group drawings in page"))
la.addWidget(rba4, 7, 2)
onedrawing_check.setChecked(True)
rba3 = QtGui.QRadioButton("A3")
la.addWidget(onedrawing_check)
la.addWidget(rba3, 7, 3)
buttonGrpFormat.addButton(rba4); buttonGrpFormat.addButton(rba3)
rba4.setChecked(True)


###
line7 = QtGui.QFrame(DialogBox)
cartridge = QtGui.QCheckBox("Cartridge")
line7.setFrameShape(QtGui.QFrame.HLine)
cartridge.setChecked(False)
line7.setFrameShadow(QtGui.QFrame.Sunken)
la.addWidget(line7)
la.addWidget(cartridge, 8, 0, 1, 2)


groupdwg = QtGui.QCheckBox("Group drawings in page")
groupdwg.setChecked(True)
la.addWidget(groupdwg, 8, 2, 1, 2)

###
box = QtGui.QDialogButtonBox(DialogBox)
box = QtGui.QDialogButtonBox(DialogBox)
box.setOrientation(QtCore.Qt.Horizontal)
box.setOrientation(QtCore.Qt.Horizontal)
box.setStandardButtons(QtGui.QDialogButtonBox.Cancel.__or__(QtGui.QDialogButtonBox.Ok))
box.setStandardButtons(QtGui.QDialogButtonBox.Cancel | QtGui.QDialogButtonBox.Ok)
la.addWidget(box)
la.addWidget(box, 9, 0, 1, cols)


###
QtCore.QObject.connect(box, QtCore.SIGNAL("accepted()"), proceed )
settings.update(dict(fname=fname, dpts=dpts, edge=buttonGrpEdge,
QtCore.QObject.connect(box, QtCore.SIGNAL("rejected()"), close )
mkdwg=mkdwg, legacylayout=legacylayout,
autoscale=autoscale, scale=scale,
papersize=buttonGrpFormat,
cartridge=cartridge, groupdwg=groupdwg))

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

}}
{{clear}}

Latest revision as of 18:56, 30 July 2022

Decodare macro ruled

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

Macro-versiune : 1.0
Data ultimei modificări : 2013-09-14
Autor: Hervé B.
Autor
Hervé B.
Descarca
None
Legătură
Versiune
1.0
Data ultimei modificări
2013-09-14
FreeCAD Versiune
None
Imprimare rapidă implicită
None
Vezi si
None

Description

Descriere

Acest macro permite să desfășurațu o suprafață rigaltă și să o desenați pe o foaie/pagină plană prestabilită: A3, A4.

Macro_unrollRuledSurface

Installation

Instalare

Copiați fișierul de cod al macrocomenzii în directorul:

  • Linux & Mac  : $home/.Freecad/Mod/UnrollRuledSurface.
  • Windows : C:\Program Files\FreeCAD0.13

Adăugați șabloane : A3_Landscape_Empty.svg A3_Landscape.svg A4_Landscape_Empty.svg A4_Landscape.svg
Cf Macro for unrolling ruled surfaces

See also: Macro for unrolling ruled surfaces.

Options

Opțiuni

  • Numărul de generatrix
  • Scala manuală sau automată
  • Formatul paginii: a3/a4, cartuș (cf. șabloane FreeCAD)
  • Desene de grup în aceeași pagină posibil.

Macro_unrollRuledSurface

Usage

Instrucțiuni de utilizare

  1. Selectați suprafețele conduse
  2. Explodează-le (cf Menu draft)
  3. Selectați suprafețele
  4. Executați macroul

Script

The latest version of the macro is here on the wiki. An earlier version can be found at UnrollRuledSurface.FCMacro, but the easiest way to install this macro is through the Std_AddonMgr Addon manager.

ToolBar Icon

Macro_unrollRuledSurface.py

# -*- coding: utf-8 -*-
#***************************************************************************
#*                                                                         *
#*   Copyright (c) 2013 - DoNovae/Herve BAILLY <hbl13@donovae.com>         *
#*   Copyright (c) 2022 - heda <heda@fc-forum>                             *
#*                                                                         *
#*   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                                                                   *
#*                                                                         *
#***************************************************************************
__Name__ = 'Unroll Ruled Surface'
__Comment__ = 'Unroll of a ruled surface and draw it on a page.'
__Author__ = 'Hervé B., heda'
__Version__ = '1.1'
__Date__ = '2022-07-24'
__License__ = 'LGPL-2.0-or-later'
__Web__ = 'https://wiki.freecad.org/Macro_Unroll_Ruled_Surface'
__Wiki__ = 'https://wiki.freecad.org/Macro_Unroll_Ruled_Surface'
__Icon__ = ''
__Help__ = ('Select ruled surfaces, Explode them (cf Draft menu), '
            'Select the surfaces, Execute the macro')
__Status__ = ''
__Requires__ = ''
__Communication__ = ''
__Files__ = ''

__doc__ = """
select a face, or several and run the macro.
a shell/solid needs to be draft/downgraded to get the faces as separate objects

the macro is intended to unroll lofted faces,
function beyond that is (in current version) a bonus

settings are not context aware, all settings not applicable are ignored.
as example, using autoscaling ignores the scale-value in the form


v1.1   (2022-07-24) py3/qt5 compat, cosmetic code changes, minor code tweaks,
       used gridlayout for form, added option to skip drawing,
       made new layout engine with techdraw,
       the drawing wb layout engine is now called "legacy"
v1.0.1 (2019-02-01) - on git
v1.0   (2013-09-14) - on wiki

note:
- unfolding sometimes works and sometimes not
- not all surfaces are theoretically possible to unroll

"""

import os
from PySide import QtGui, QtCore
import FreeCAD, FreeCADGui
import Part, Draft

Vector = FreeCAD.Base.Vector
PrintMessage = FreeCAD.Console.PrintMessage
PrintError = FreeCAD.Console.PrintError

settings = dict()
unroll_l = []
dwgtpllegacy = 'Mod/Drawing/Templates'
dwgtpl = 'Mod/TechDraw/Templates'


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

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

def ending():
    PrintMessage("UnrollRuledSurface: end.\n")
    PrintMessage("===========================================\n")
    FreeCAD.ActiveDocument.recompute()


def proceed():
    QtGui.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor)

    PrintMessage("===========================================\n")
    PrintMessage("UnrollRuledSurface: start.\n")
    try:
        sts = lambda s: settings.get(s)

        file_name = sts("fname").text()
        pts_nbr = float(sts("dpts").text())
        makedwg = sts("mkdwg").isChecked()
        leglay = sts("legacylayout").isChecked()
        scale = float(sts("scale").text()) # ignored if autoscale is set
        scale_auto = sts("autoscale").isChecked()
        edge0 = sts("edge").checkedId() == -2
        a3 = sts("papersize").checkedId() == -3
        cartridge = sts("cartridge").isChecked()
        onedrawing = sts("groupdwg").isChecked()

        PrintMessage("UnrollRuledSurface.file_name: {}\n".format(file_name))
        PrintMessage("UnrollRuledSurface.pts_nbr: {}\n".format(pts_nbr))
        PrintMessage("UnrollRuledSurface.edge0: {}\n".format(edge0))
        PrintMessage("UnrollRuledSurface.makedwg: {}\n".format(makedwg))
        PrintMessage("UnrollRuledSurface.leglay: {}\n".format(leglay))
        PrintMessage("UnrollRuledSurface.scale_check: {}\n".format(scale_auto))
        PrintMessage("UnrollRuledSurface.scale: {}\n".format(scale))
        PrintMessage("UnrollRuledSurface.a3_check: {}\n".format(a3))
        PrintMessage("UnrollRuledSurface.cartridge: {}\n".format(cartridge))
        PrintMessage("UnrollRuledSurface.onedrawing: {}\n".format(onedrawing))
    except:
        msg = "UnrollRuledSurface: wrong inputs...\n"
        PrintError(msg)
        errorDialog(msg)
        QtGui.QApplication.restoreOverrideCursor()
        DialogBox.hide()
        ending()
        return

    QtGui.QApplication.restoreOverrideCursor()
    DialogBox.hide()
    unrollRS = unrollRuledSurface(file_name, pts_nbr, edge0)

    ## Get selection
    sel = FreeCADGui.Selection.getSelection()
    if not sel:
        PrintMessage("UnrollRuledSurface: no selection...\n")
        ending()
        return

    faceid = 0
    objnames_l, objnames0_l = [], []
    grp = FreeCAD.ActiveDocument.addObject("App::DocumentObjectGroup",
                                           "{}_objs".format(file_name))

    for objid, obji in enumerate(sel):
        shape = obji.Shape
        faces = shape.Faces
        for idx in range(len(faces)):
            msg = "UnrollRuledSurface.proceed: ObjId = {}, faceId = {}\n"
            PrintMessage(msg.format(objid, faceid))
            name = obji.Name
            if len(faces) > 1:
                name = "{}.face_{}".format(name, idx)
            obj = unrollRS.unroll(faces[idx], name)
            obj.ViewObject.Visibility = not makedwg
            grp.addObject(obj)

        objnames_l.append([obj, name])
        objnames0_l.append([obji, name])
        faceid += 1

    if not makedwg:
        ending()
        return

    if leglay:
        idx = 0
        while len(objnames_l) > 0:
            draw = Drawing2dLegacy(scale, scale_auto, a3,
                                   cartridge, onedrawing,
                                   "{}_page{:02}".format(file_name, idx))
            objnames_l = draw.all2d(objnames_l)
            idx += 1
            msg = "UnrollRuledSurface: obj_l = {}\n"
            PrintMessage(msg.format(len(objnames_l)))

    else:
        draw = Drawing2d(scale, scale_auto, a3, cartridge)
        n = 4 if onedrawing else 1
        chunks = [objnames_l[i:i + n] for i in range(0, len(objnames_l), n)]
        for i, chunk in enumerate(chunks, start=1):
            draw.drawpage(chunk, "{}_page{:02}".format(file_name, i))

    ending()


def close():
    DialogBox.hide()

def getType(obj):
    return type(obj).__name__


class unrollRuledSurface:
    """
    unroll ruled surfaces
    :file_name: ouput file
    :pts_nbr: nbr point of discretization
    """
    def __init__(self, file_name, pts_nbr, edge0):
        self.doc = FreeCAD.ActiveDocument
        self.file_name = file_name
        self.pts_nbr = int(pts_nbr)
        self.edge0 = edge0
        msg = "UnrollRuledSurface.unroll - file_name: {}, pts_nbr: {}\n"
        PrintMessage(msg.format(file_name, pts_nbr))


    def discretize(self, curve):
        """discretize a curve"""
        if getType(curve) in ('GeomLineSegment', 'GeomCircle'):
            sd = curve.discretize(self.pts_nbr)
        elif getType(curve) == 'GeomBSplineCurve':
            nodes = curve.getPoles()
            spline = Part.BSplineCurve()
            spline.buildFromPoles(nodes)
            sd = spline.discretize(self.pts_nbr)
        else:
            sd = curve.discretize(self.pts_nbr)
        return sd

    def nbpoles(self, curve):
        """find number of poles for a curve"""
        if getType(curve) == 'GeomLineSegment':
            nbpol=1
        elif getType(curve) == 'GeomBSplineCurve':
            nbpol=curve.NbPoles
        elif getType(curve) == 'GeomCircle':
            nbpol=2
        elif getType(curve) == 'GeomBezierCurve':
            nbpol=4
        else:
            nbpol=0

        msg = "UnrollRulrdSurface.nbpole {:s} = {:d}\n"
        PrintMessage(msg.format(getType(curve), nbpol))
        return nbpol

    def unroll(self, face, name):
        """unrolls a face composed of 2 to 4 edges"""
        nbredges = len(face.Edges)
        msg = "UnrollRuledSurface.unroll: Edge Nbr = {}\n"
        PrintMessage(msg.format(nbredges))

        if nbredges == 2:
            e1, e2 = face.Edges
            sd1 = e1.Curve.discretize(self.pts_nbr)
            sd2 = e2.Curve.discretize(self.pts_nbr)

        elif nbredges == 3:
            e1, _, e2 = face.Edges
            sd1 = e1.Curve.discretize(self.pts_nbr)
            sd2 = e2.Curve.discretize(self.pts_nbr)

        else:
            E0, E1, E2, E3, *_ = face.Edges
            ## 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)
            msg = ("UnrollRuledSurface.unroll: nbpol0= {:d}, nbpol1= {:d},"
                   " nbpol2= {:d}, nbpol3= {:d}\n")
            PrintMessage(msg.format(nbpol0, nbpol1, nbpol2, nbpol3))

            if self.edge0:
                e1, e2 = E0, E2
                v = self.discretize(E1)
                v0, v1 = v[0], v[self.pts_nbr-1]
            else:
                e1, e2 = E1, E3
                v = self.discretize(E2)
                v0, v1 = v[0], 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)
        msg = "UnrollRuledSurface.unroll: size devlxy_l: {}\n"
        PrintMessage(msg.format(len(devlxy_l)))
        p = self.doc.addObject("Part::Polygon", name)
        p.Nodes = devlxy_l
        self.doc.recompute()
        FreeCADGui.ActiveDocument.ActiveView.fitAll()
        return p

    def vect_copy(self, vect):
        """returns copy of vector"""
        return vect.add(Vector())

    def vect_cos(self, vect1, vect2):
        """returns cosine angle between 2 vectors"""
        return vect1.dot(vect2) / vect1.Length / vect2.Length

    def vect_sin(self, vect1, vect2):
        """returns absolute sinus angle between 2 vectors"""
        v = Vector()
        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
        return v.Length / vect1.Length / vect2.Length

    def devlxyz(self, vect1, vect2):
        """
        unrolls a face composed of 4 edges
        args: vect1, vect2 --> 2 edges of the shape
        returns: dvlxy
        """
        lenv1, lenv2 = len(vect1), len(vect2)
        if lenv1 != lenv2 or lenv1 != self.pts_nbr or lenv2 != self.pts_nbr:
            msg = ("UnrollRuledSurface.devlxyz: incompatility of sizes vect1,"
                   " vect2, pts_nbr: ({}, {}, {})\n")
            PrintError(msg.format(lenv1, lenv2, self.pts_nbr))
            errorDialog(msg)

        devlxy_l, devl1xy_l, devl2xy_l = [], [], []
        errormax = 0.0
        ## Init unroll
        ## AB
        a1b1 = vect2[0].sub(vect1[0])
        oa1 = Vector(0, 0, 0)
        devl1xy_l.append(oa1) #A1
        ob1 = Vector(a1b1.Length, 0, 0)
        devl2xy_l.append(ob1) #B1
        #self.draw_line(devl1xy_l[0], devl2xy_l[0])
        #self.draw_line(vect1[0], vect2[0])
        for j in range(1, self.pts_nbr):

            ## AB
            ab = vect2[j-1].sub(vect1[j-1])
            #self.draw_line(vect1[j-1], vect2[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 = 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
            #self.draw_line(devl1xy_l[j], devl2xy_l[j])
            c1d1 = devl2xy_l[j].sub(devl1xy_l[j])
            if ab.Length != 0:
                abl = ab.Length
                errormax = max(errormax, abs(abl - c1d1.Length) / abl)

        msg = "UnrollRuledSurface Error cd,c1d1: {:.1f} %\n"
        PrintMessage(msg.format(errormax*100))

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

        return devlxy_l


    def draw_line(self, vect0, vect1):
        """draws a Part.Line between vect0 & vect1"""
        l = Part.LineSegment()
        l.StartPoint = vect0
        l.EndPoint = vect1
        self.doc.addObject("Part::Feature", "Line").Shape = l.toShape()


class Scale:
    """keeps autoscaling to integers"""
    def __init__(self, scale):
        self.scale = scale if scale >= 1 else 1 / scale
        self.scale = int(self.scale)
        self.inverted = scale >= 1

    def get(self):
        if self.inverted:
            return self.scale, '{}:1'.format(self.scale)
        else:
            return 1/self.scale, '1:{}'.format(self.scale)


class Drawing2d:
    """
    TechDraw wb layout engine, diffeent logic compared to drawing wb layout.
    serves basic purpose...
    """
    def __init__(self, scale, scale_auto, a3, cartridge):

        self.a3 = a3
        self.scale = scale
        self.scale_auto = scale_auto
        self.cartridge = cartridge
        if a3:
            self.WH = 420, 297
        else:
            self.WH = 297, 210
        self.doc = FreeCAD.ActiveDocument


    def _mkquadrants(self, nbr):
        """centers of quadrants w/o margin"""
        w, h = self.WH
        w2, h2 = w/2, h/2
        w4, h4 = w/4, h/4

        q = {1: [[w, h], [[w2, h2]]],
             2: [[w2, h], [[w2 - w4, h2], [w2 + w4, h2]]],
             3: [[w2, h2], [[w2 - w4, h2 + h4], [w2 + w4, h2 + h4],
                            [w2 - w4, h2 - h4]]],
             4: [[w2, h2], [[w2 - w4, h2 + h4], [w2 + w4, h2 + h4],
                            [w2 - w4, h2 - h4], [w2 + w4, h2 - h4]]]}
        return q.get(nbr)


    def newPage(self, doc, page_name):
        freecad_dir = os.path.join(FreeCAD.getResourceDir(), dwgtpl)
        page = doc.addObject('TechDraw::DrawPage', page_name)
        template = self.doc.addObject('TechDraw::DrawSVGTemplate', 'Template')
        size = 'A3' if self.a3 else 'A4'
        frame = 'TD' if self.cartridge else '_blank'
        template.Template = freecad_dir + '/{}_Landscape{}.svg'.format(size, frame)
        page.Template = template
        return page

    def drawpage(self, faces, page_name):
        """max 4 per page, simple layout with halfs or quadrants"""
        page = self.newPage(self.doc, page_name)
        [W, H], ll = self._mkquadrants(len(faces))
        for [face, name], [x0, y0] in zip(faces, ll):
            bb = face.Shape.BoundBox
            xr, yr = W / bb.XLength, H / bb.YLength
            adjust = 0.7 if self.cartridge else 0.85
            scale = min(xr, yr) * adjust if self.scale_auto else self.scale
            scale, scr = Scale(scale).get()
            bb.scale(scale, scale, 1) # unrolled faces in xy-plane

            TopView = self.doc.addObject('TechDraw::DrawViewPart', name + ' view')
            page.addView(TopView)
            TopView.Source = face
            TopView.Direction = (0, 0, 1)
            TopView.XDirection = (1, 0, 0)
            TopView.Scale = scale
            TopView.X = x0
            TopView.Y = y0

            Text = self.doc.addObject('TechDraw::DrawViewAnnotation', name + ' txt')
            page.addView(Text)
            Text.Text = '{} [{}]'.format(name, scr)
            Text.recompute() # for size
            Text.X = x0
            yt = y0 - bb.YLength / 2 - Text.TextSize.Value * 1.5
            Text.Y = max(1, yt)

        self.doc.recompute()
        page.ViewObject.doubleClicked()
        FreeCADGui.runCommand('TechDraw_ToggleFrame', 0)


class Drawing2dLegacy:
    """
    makes 2d drawing with Drawing wb (original layout engine, now legacy)
    - obj_l: list of objects
    """
    ## untouched logic in v1.1
    def __init__(self, scale, scale_auto, a3, cartridge, onedrawing, page_str):
        self.TopX_H = self.TopY_H = 0
        self.TopX_V = self.TopY_V = 0
        self.TopX_Hmax = self.TopY_Hmax = 0
        self.TopX_Vmax = self.TopY_Vmax = 0
        self.a3 = a3
        self.scale = scale
        self.scale_auto = scale_auto
        self.cartridge = cartridge
        self.onedrawing = onedrawing
        self.marge = 6
        if self.a3:
            self.L, self.H = 420, 297
        else:
            self.L, self.H = 297, 210
        self.name = page_str

    def newPage(self):
        freecad_dir = os.path.join(FreeCAD.getResourceDir(), dwgtpllegacy)
        doc = FreeCAD.ActiveDocument
        page = doc.addObject('Drawing::FeaturePage', self.name)
        size = 'A3' if self.a3 else 'A4'
        frame = '' if self.cartridge else '_plain'
        page.Template = freecad_dir + '/{}_Landscape{}.svg'.format(size, frame)
        return page


    def all2d(self, objname_l):
        obj1_l = []
        for objid in range(len(objname_l)):
            if objid == 0 or not self.onedrawing:
                page = self.newPage()
            obj1_l.extend(self.done(objid, objname_l[objid]))
        return obj1_l

    def done(self, idx, objname):
        obj_l = []
        obj, objname = objname
        marge = self.marge
        bb = obj.Shape.BoundBox
        xmax = bb.XMax - bb.XMin
        ymax = bb.YMax - bb.YMin

        if ymax > xmax:
            Draft.rotate(obj, 90)
        Draft.move(obj, Vector(-bb.XMin, -bb.YMin, 0))
        xmax = bb.XMax - bb.XMin
        ymax = bb.YMax - bb.YMin

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

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

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


        if idx == 0 or not self.onedrawing:
            PrintMessage("Drawing2d: init\n")
            TopX = self.TopX_H = marge * 2
            TopY = self.TopY_H = marge * 2
            self.TopX_H = self.TopX_H + xmax * scale + 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 + marge)
            self.TopX_Vmax = max(self.TopX_Vmax, self.TopX_Hmax)
            self.TopX_V = max(self.TopX_Vmax, self.TopX_V)
            self.TopY_V = marge * 2

        elif self.onedrawing:
            if self.TopX_H + xmax * scale < self.L:
                if self.TopY_H + ymax * scale + marge * 2 < self.H:
                    ## H Add at right on same horizontal line
                    PrintMessage("Drawing2d: horizontal\n")
                    TopX, TopY = self.TopX_H, self.TopY_H
                    self.TopX_H = self.TopX_H + xmax * scale + marge
                    self.TopX_Hmax = max(self.TopX_Hmax, self.TopX_H)
                    self.TopY_Hmax = max(self.TopY_Hmax,
                                         self.TopY_H + ymax * scale + 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
                    PrintMessage("Drawing2d: vertival\n")
                    if (self.TopX_V + ymax * scale + 2 * marge < self.L
                        and self.TopY_V + xmax * scale + 2 * marge < self.H):
                        Draft.rotate(obj, 90)
                        Draft.move(obj, Vector(-bb.XMin, -bb.YMin, 0))
                        x0 = xmax; xmax = ymax; ymax = x0
                        self.TopX_V = max(self.TopX_Vmax, self.TopX_V)
                        TopX, TopY = self.TopX_V, self.TopY_V
                        self.TopX_V = self.TopX_V + xmax * scale + marge
                        self.TopY_Vmax = max(self.TopY_Vmax,
                                             self.TopY_V + ymax * scale + marge)

                    else:
                        obj_l.append([obj, self.name])
                        return obj_l

            else:
                ## H Carriage return
                if self.TopY_Hmax + ymax * scale + self.marge*2 < self.H:
                    msg = "Drawing2d: carriage return: {} > {}\n"
                    PrintMessage(msg.format(self.TopY_H + ymax * scale, 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_Hmax + 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
                    msg = "Drawing2d: vertival: {} , {}\n"
                    PrintMessage(msg.format(self.TopX_V, self.TopX_Vmax))
                    if (self.TopX_V + ymax * scale + 2 * marge < self.L
                        and self.TopY_V + xmax * scale + 2 * marge < self.H):
                        Draft.rotate(obj, 90)
                        Draft.move(obj, Vector(-bb.XMin, -bb.YMin, 0))
                        x0 = xmax; xmax = ymax; ymax = x0
                        TopX, TopY = self.TopX_V, self.TopY_V
                        self.TopX_V = self.TopX_V + xmax * scale + marge
                        self.TopY_Vmax = max(self.TopY_Vmax,
                                             self.TopY_V + ymax * scale + marge)
    
                    else:
                        obj_l.append([obj, objname])
                        return obj_l

        doc = FreeCAD.ActiveDocument
        page = doc.getObject(self.name)

        Text = doc.addObject('Drawing::FeatureViewAnnotation', f"{objname}_txt")
        Text.Text = objname
        Text.X = TopX + xmax * scale / 2
        Text.Y = TopY + ymax * scale / 2
        Text.Scale = 2

        TopView = doc.addObject('Drawing::FeatureViewPart', objname)
        TopView.Source = obj
        TopView.Direction = (0, 0, 1)
        TopView.Rotation = 0
        TopView.X = TopX
        TopView.Y = TopY
        TopView.ShowHiddenLines = False
        TopView.Scale = scale
        page.addObject(TopView)
        page.addObject(Text)
        doc.recompute()
        page.ViewObject.doubleClicked()
        return obj_l



#####################################
###   Dialog Box
#####################################

DialogBox = QtGui.QDialog()
DialogBox.setWindowTitle("UnrollRuledSurface")
la = QtGui.QGridLayout(DialogBox)
la.setSpacing(7)
buttonGrpEdge = QtGui.QButtonGroup(DialogBox)
buttonGrpFormat = QtGui.QButtonGroup(DialogBox)

cols = 4
la.addWidget(QtGui.QLabel("File Name"), 0, 0, 1, cols)
fname = QtGui.QLineEdit("UnrollSurface")
la.addWidget(fname, 1, 0, 1, cols)

la.addWidget(QtGui.QLabel("Discretization Points Nbr"), 2, 0, 1, cols)
dpts = QtGui.QLineEdit("30")
la.addWidget(dpts, 3, 0, 1, 2)

###
la.addWidget(QtGui.QLabel("Generatrices from edge:"), 4, 0, 1, 2)
edgezero = QtGui.QRadioButton("0 to 3")
la.addWidget(edgezero, 4, 2)
edgeone = QtGui.QRadioButton("1 to 4")
la.addWidget(edgeone, 4, 3)
buttonGrpEdge.addButton(edgezero); buttonGrpEdge.addButton(edgeone)
edgezero.setChecked(True)

###
mkdwg = QtGui.QCheckBox("Make drawing")
mkdwg.setChecked(True)
la.addWidget(mkdwg, 5, 0, 1, 2)

legacylayout = QtGui.QCheckBox("Legacy layout")
legacylayout.setChecked(False)
la.addWidget(legacylayout, 5, 2, 1, 2)


###
autoscale = QtGui.QCheckBox("Auto scale")
autoscale.setChecked(True)
la.addWidget(autoscale, 6, 0, 1, 2)

la.addWidget(QtGui.QLabel("Scale"), 6, 2)
scale = QtGui.QLineEdit("1")
la.addWidget(scale, 6, 3)

###
la.addWidget(QtGui.QLabel("Paper size:"), 7, 0, 1, 2)
rba4 = QtGui.QRadioButton("A4")
la.addWidget(rba4, 7, 2)
rba3 = QtGui.QRadioButton("A3")
la.addWidget(rba3, 7, 3)
buttonGrpFormat.addButton(rba4); buttonGrpFormat.addButton(rba3)
rba4.setChecked(True)

###
cartridge = QtGui.QCheckBox("Cartridge")
cartridge.setChecked(False)
la.addWidget(cartridge, 8, 0, 1, 2)

groupdwg = QtGui.QCheckBox("Group drawings in page")
groupdwg.setChecked(True)
la.addWidget(groupdwg, 8, 2, 1, 2)

###
box = QtGui.QDialogButtonBox(DialogBox)
box.setOrientation(QtCore.Qt.Horizontal)
box.setStandardButtons(QtGui.QDialogButtonBox.Cancel | QtGui.QDialogButtonBox.Ok)
la.addWidget(box, 9, 0, 1, cols)

###
settings.update(dict(fname=fname, dpts=dpts, edge=buttonGrpEdge,
                     mkdwg=mkdwg, legacylayout=legacylayout,
                     autoscale=autoscale, scale=scale,
                     papersize=buttonGrpFormat,
                     cartridge=cartridge, groupdwg=groupdwg))

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