Macro Unfold Box/cs: Difference between revisions
Renatorivo (talk | contribs) No edit summary |
(Updating to match new version of source page) |
||
(19 intermediate revisions by 3 users not shown) | |||
Line 1: | Line 1: | ||
<languages/> |
|||
{{Macro|Icon=Text-x-python|Name=Macro Unfold Box|Description=The macro allows to unfold the surfaces of a box of any shape and to draw them on a page.|Author=Hervé B.}} |
|||
<div class="mw-translate-fuzzy"> |
|||
{{Macro/cs |
|||
|Name=Macro Unfold Box |
|||
|Icon=Macro_Unfold_Box.png |
|||
|Translate=Makro Rozbal Krabici |
|||
|Description=Toto makro rozloží povrchy krabice jakéhokoliv tvaru a nakreslí je na stránku.<BR/>{{ColoredText|#ff0000|#ffff00|This macro must be upgraded cause error indentation and space mixed with tabulation}} |
|||
|Author=Hervé B. |
|||
|Version=1.0 |
|||
|Date=2013-09-20 |
|||
|FCVersion= |
|||
|Download=[https://www.freecadweb.org/wiki/images/e/e4/Macro_Unfold_Box.png ToolBar Icon] |
|||
}} |
|||
</div> |
|||
==Description== |
|||
<div class="mw-translate-fuzzy"> |
|||
==Deskriptivní== |
|||
Toto makro rozloží povrchy krabice jakéhokoliv tvaru a nakreslí je na stránku. |
Toto makro rozloží povrchy krabice jakéhokoliv tvaru a nakreslí je na stránku. |
||
</div> |
|||
[[File:Macro_unfoldBox1.png|480px]] |
|||
<div class="mw-translate-fuzzy"> |
|||
[[File:Macro_unfoldBox1.png|480px]] |
|||
{{Caption|Macro_unfoldBox}} |
|||
</div> |
|||
== Installation == |
|||
[[File:Macro_unfoldBox1.png|480px|Macro_unfoldBox]] |
|||
<div class="mw-translate-fuzzy"> |
|||
=== Instalace === |
|||
==Instalace== |
|||
Zkopírujte kód makra do souboru v adresáři : |
Zkopírujte kód makra do souboru v adresáři : |
||
*'''Linux & Mac''' : $home/.Freecad/Mod/unfoldBox. |
*'''Linux & Mac''' : $home/.Freecad/Mod/unfoldBox. |
||
Line 12: | Line 37: | ||
Přidejte šablony : A3_Landscape_Empty.svg A3_Landscape.svg A4_Landscape_Empty.svg A4_Landscape.svg<br /> |
Přidejte šablony : 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=4587 Makro pro rozložení povrchů krabice] |
Cf [http://forum.freecadweb.org/viewtopic.php?f=17&t=4587 Makro pro rozložení povrchů krabice] |
||
</div> |
|||
Forum topic: [http://forum.freecadweb.org/viewtopic.php?f=17&t=4587 Macro for unfolding box surfaces]. |
|||
=== Volby === |
|||
== Options == |
|||
<div class="mw-translate-fuzzy"> |
|||
==Volby== |
|||
# Měřítko ruční nebo automatické |
# Měřítko ruční nebo automatické |
||
# Formát stránky: a3/a4, cartridge (cf FreeCAD šablony) |
# Formát stránky: a3/a4, cartridge (cf FreeCAD šablony) |
||
# Je-li to možné seskup výkresy na stejnou stránku. |
# Je-li to možné seskup výkresy na stejnou stránku. |
||
# Sešít nebo nesešít okraje kousků. |
# Sešít nebo nesešít okraje kousků. |
||
</div> |
|||
[[File:Macro_UnFoldBox_start_form.png|Macro_unfoldBox]] |
|||
== Usage == |
|||
<div class="mw-translate-fuzzy"> |
|||
=== Použití === |
|||
==Použití== |
|||
# Vyber krabici vytvořenou například pomocí nástroje Díl::Profilování. |
# Vyber krabici vytvořenou například pomocí nástroje Díl::Profilování. |
||
# Rozděl jej (cf Draft menu) do nárysů |
# Rozděl jej (cf Draft menu) do nárysů |
||
# Vyber povrchy |
# Vyber povrchy |
||
# Spusť makro |
# Spusť makro |
||
</div> |
|||
The unfolding algorithm will place the faces on the XY plane, however it seldom does the unfolding correctly. Thus some manual post-processing is needed to get to desired result, as shown in the picture below. |
|||
=== Python kód === |
|||
'''Makro_Rozlož krabici''' |
|||
[[File:MacroUnFoldBoxInstruction.png|Macro_unfoldBox]] |
|||
Starting point is a box like the upper left picture. |
|||
<syntaxhighlight> |
|||
#*************************************************************************** |
|||
#* * |
|||
#* Copyright (c) 2013 - DoNovae/Herve BAILLY <hbl13@donovae.com> * |
|||
#* * |
|||
#* This program is free software; you can redistribute it and/or modify * |
|||
#* it under the terms of the GNU Lesser General Public License (LGPL) * |
|||
#* as published by the Free Software Foundation; either version 2 of * |
|||
#* the License, or (at your option) any later version. * |
|||
#* for detail see the LICENCE text file. * |
|||
#* * |
|||
#* This program is distributed in the hope that it will be useful, * |
|||
#* but WITHOUT ANY WARRANTY; without even the implied warranty of * |
|||
#* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * |
|||
#* GNU Library General Public License for more details. * |
|||
#* * |
|||
#* You should have received a copy of the GNU Library General Public * |
|||
#* License along with this program; if not, write to the Free Software * |
|||
#* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * |
|||
#* USA * |
|||
#* * |
|||
#*************************************************************************** |
|||
# Select all faces and run the macro. |
|||
##################################### |
|||
# Orient to Top View. |
|||
# Macro unfoldBox |
|||
# Toggle the visibility on the created set of faces, the individual clones are to be visible, not the compound. |
|||
# Unroll of a ruled surface |
|||
# Use [[Draft_Move|Draft Move]] and [[Draft_Rotate|Draft Rotate]] to reposition the unfolded cloned faces (a handful of clicks per face when snapping is used). |
|||
##################################### |
|||
# The drawing updates automatically to the new positions. |
|||
import FreeCAD , FreeCADGui , Part, Draft, math, Drawing , PySide, os |
|||
from PySide import QtGui,QtCore |
|||
The finished result is shown in the lower right picture above. |
|||
from FreeCAD import Base |
|||
fields_l = [] |
|||
==Skript== |
|||
unroll_l = [] |
|||
ToolBar icon [[Image:Macro_Unfold_Box.png]] |
|||
'''Macro_unfoldBox.FCMacro''' |
|||
<!--DO NOT CHANGE THE <pre> </pre> TAG, code contains pipe or space character which breaks the wiki macro--> |
|||
<pre> |
|||
# -*- coding: utf-8 -*- |
|||
""" |
|||
FreeCAD Macro unfoldBox. |
|||
Unfolding of planar surfaces |
|||
""" |
|||
################################################## |
|||
# SEE https://wiki.freecadweb.org/Macro_Unfold_Box |
|||
# *************************************************************************** |
|||
# * * |
|||
# * 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__ = 'Unfold Box' |
|||
__Comment__ = 'Unfolds planar surfaces and draws them on a page.' |
|||
__Author__ = 'Hervé B., heda' |
|||
__Version__ = '1.1' |
|||
__Date__ = '2022-07-28' |
|||
__License__ = 'LGPL-2.0-or-later' |
|||
__Web__ = 'https://wiki.freecad.org/Macro_Unfold_Box' |
|||
__Wiki__ = 'https://wiki.freecad.org/Macro_Unfold_Box' |
|||
__Icon__ = '' |
|||
__Help__ = ('Select surfaces, Explode them (cf Draft menu Downgrade), ' |
|||
'Select the exploded 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 unfold a set of planar faces, |
|||
in current state, most of the times it does not place the faces correctly. |
|||
thus one needs to do the final placement of the clones manually, with Draft/Move & Rotate |
|||
regardless, the macro saves a lot of clicks compared to a fully manual unfold |
|||
settings are not context aware, all settings not applicable are ignored. |
|||
as example, using autoscaling ignores the scale-value in the form |
|||
sew is always enabled, thus the drawing |
|||
is always the sewed shape on a single page |
|||
v1.1 (2022-07-28) py3/qt5 compat, cosmetic code changes, |
|||
minor code tweaks for evolved fc-api |
|||
added option to skip drawing, made layout engine with techdraw, |
|||
the drawing wb layout engine is now called "legacy" |
|||
v1.0.1 (2020-03-10) - on wiki |
|||
v1.0 (2013-09-14) - on wiki |
|||
note: |
|||
- unfolding occasionally work as expected, but mostly not |
|||
- anyone is free to improve functionality |
|||
""" |
|||
import os, math |
|||
from PySide import QtGui, QtCore |
|||
import FreeCAD, FreeCADGui |
|||
import Draft |
|||
Vector = FreeCAD.Base.Vector |
|||
Units = FreeCAD.Units |
|||
PrintMessage = FreeCAD.Console.PrintMessage |
|||
PrintError = FreeCAD.Console.PrintError |
|||
fields_l = [] |
|||
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, |
diag = QtGui.QMessageBox(QtGui.QMessageBox.Critical, 'Error Message', msg) |
||
diag.setWindowFlags( |
diag.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint) |
||
diag.exec_() |
diag.exec_() |
||
##################################### |
|||
# Function proceed |
|||
##################################### |
|||
def proceed(): |
def proceed(): |
||
QtGui. |
QtGui.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor) |
||
PrintMessage('===========================================\n') |
|||
PrintMessage('unfoldBox: start.\n') |
|||
try: |
try: |
||
file_name = fields_l[0].text() |
|||
makedwg = makedwg_check.isChecked() |
|||
leglay = leglay_check.isChecked() |
|||
scale = float(fields_l[1].text()) |
|||
scale_auto = scale_check.isChecked() |
|||
a3 = a3_check.isChecked() |
|||
cartridge = cartridge_check.isChecked() |
|||
onedrawing = onedrawing_check.isChecked() |
|||
FreeCAD.Console.PrintMessage("unfoldBox.file_name: "+file_name+"\n") |
|||
sewed = sewed_check.isChecked() |
|||
FreeCAD.Console.PrintMessage("unfoldBox.scale: "+str(scale)+"\n") |
|||
PrintMessage('unfoldBox.file_name: {}\n'.format(file_name)) |
|||
PrintMessage('unfoldBox.makedwg: {}\n'.format(makedwg)) |
|||
PrintMessage('unfoldBox.leglay: {}\n'.format(leglay)) |
|||
PrintMessage('unfoldBox.scale: {}\n'.format(scale)) |
|||
PrintMessage('unfoldBox.scale_check: {}\n'.format(scale_auto)) |
|||
PrintMessage('unfoldBox.a3_check: {}\n'.format(a3)) |
|||
except: |
|||
PrintMessage('unfoldBox.cartridge: {}\n'.format(cartridge)) |
|||
msg="unfoldBox: wrong inputs...\n" |
|||
PrintMessage('unfoldBox.onedrawing: {}\n'.format(onedrawing)) |
|||
FreeCAD.Console.PrintError(msg) |
|||
PrintMessage('unfoldBox.sewed: {}\n'.format(sewed)) |
|||
errorDialog(msg) |
|||
except: |
|||
msg = 'unfoldBox: wrong inputs...\n' |
|||
PrintError(msg) |
|||
errorDialog(msg) |
|||
QtGui. |
QtGui.QApplication.restoreOverrideCursor() |
||
DialogBox.hide() |
DialogBox.hide() |
||
# |
|||
# Get selection |
|||
# |
|||
sel=FreeCADGui.Selection.getSelection() |
|||
faceid=0 |
|||
objnames_l=[] |
|||
tree_l=[] |
|||
grp=FreeCAD.activeDocument().addObject("App::DocumentObjectGroup", str(file_name)) |
|||
for objid in range( sel.__len__() ): |
|||
obj=Draft.clone(sel[objid]) |
|||
grp.addObject(obj) |
|||
objnames_l.append( [ obj , sel[objid].Name ] ) |
|||
## Get selection |
|||
unfold=unfoldBox() |
|||
sel = FreeCADGui.Selection.getSelection() |
|||
if sewed : |
|||
if not sel or len(sel) < 2: |
|||
objnames_l=unfold.done(objnames_l) |
|||
PrintMessage('unfoldBox: requires at least 2 selected faces, ending.\n') |
|||
grp.addObject(objnames_l[0][0]) |
|||
return |
|||
for objid in range( objnames_l.__len__() ): |
|||
unfold.moveXY(objnames_l[objid][0]) |
|||
doc = FreeCAD.ActiveDocument |
|||
id=0 |
|||
while objnames_l.__len__() > 0: |
|||
draw=Drawing2d( scale, scale_auto , a3 , cartridge , onedrawing , FreeCAD.activeDocument().Name , "Page"+str(id) ) |
|||
objnames_l=draw.all( objnames_l ) |
|||
id=id+1 |
|||
FreeCAD.Console.PrintMessage("unfoldBox: obj_l= "+str(objnames_l.__len__())+"\n") |
|||
objnames_l=[] |
|||
FreeCAD.Console.PrintMessage("unfoldBox: end.\n") |
|||
grp = doc.addObject('App::DocumentObjectGroup', str(file_name)) |
|||
FreeCAD.Console.PrintMessage("===========================================\n") |
|||
for objid in range(len(sel)): |
|||
obj = Draft.make_clone(sel[objid]) |
|||
grp.addObject(obj) |
|||
objnames_l.append([ obj, sel[objid].Name ]) |
|||
doc.recompute() |
|||
unfold = unfoldBox(doc) |
|||
if sewed: |
|||
objnames_l = unfold.done(objnames_l) |
|||
grp.addObject(objnames_l[0][0]) |
|||
else: |
|||
for objid in range(len(objnames_l)): |
|||
unfold.moveXY(objnames_l[objid][0]) |
|||
if not makedwg: |
|||
return |
|||
if leglay: |
|||
idx = 0 |
|||
while len(objnames_l) > 0: |
|||
draw = Drawing2dLegacy(scale, scale_auto, a3, |
|||
cartridge, onedrawing, |
|||
doc.Name, 'Page'+str(idx)) |
|||
objnames_l = draw.all_(objnames_l) |
|||
idx += 1 |
|||
PrintMessage('unfoldBox: obj_l= {}\n'.format(len(objnames_l))) |
|||
else: |
|||
draw = Drawing2d(scale, scale_auto, a3, cartridge) |
|||
draw.drawpage(objnames_l[0], "{}Page".format(file_name)) |
|||
PrintMessage('unfoldBox: end.\n') |
|||
PrintMessage('===========================================\n') |
|||
##################################### |
|||
# Function close |
|||
##################################### |
|||
def close(): |
def close(): |
||
DialogBox.hide() |
DialogBox.hide() |
||
def getType(obj): |
|||
return type(obj).__name__ |
|||
def mkmm(l): |
|||
return Units.Quantity(l, Units.Length) |
|||
##################################### |
|||
# Class unfoldBox |
|||
##################################### |
|||
class unfoldBox: |
class unfoldBox: |
||
def __init__(self, doc): |
|||
##################################### |
|||
PrintMessage('unfoldBox.unfoldBox\n') |
|||
# Function __init__ |
|||
self.doc = doc |
|||
##################################### |
|||
self.LIMIT = 0.0001 |
|||
FreeCAD.Console.PrintMessage("unfoldBox.unfoldBox\n") |
|||
self.LIMIT=0.0001 |
|||
def done(self, objnames_l): |
|||
tree_l = self.makeTree(objnames_l) |
|||
for idx in range(len(objnames_l)): |
|||
face = objnames_l[idx] |
|||
self.moveXY(face[0]) |
|||
self.sew(objnames_l, tree_l) |
|||
return self.fusion(objnames_l) |
|||
def getEndPoints(self, edge): |
|||
##################################### |
|||
return [v.Point for v in edge.Vertexes] |
|||
# Function done |
|||
##################################### |
|||
def done(self,objnames_l): |
|||
tree_l=self.makeTree(objnames_l) |
|||
for id in range( objnames_l.__len__() ): |
|||
face=objnames_l[id] |
|||
self.moveXY(face[0]) |
|||
self.sew( objnames_l , tree_l ) |
|||
return self.fusion(objnames_l) |
|||
def makeTree(self, objnames_l): |
|||
## Initialisation of tree_l. |
|||
##################################### |
|||
tree_l = [] |
|||
# Function makeTree |
|||
for k in range(len(objnames_l)): |
|||
##################################### |
|||
facek = objnames_l[k][0] |
|||
facekEdges = facek.Shape.Edges |
|||
# |
|||
facek_l = [] |
|||
# Initialisation of tree_l |
|||
for i in range(len(facekEdges)): |
|||
# |
|||
if False and getType(facekEdges[i].Curve) != 'GeomLineSegment': |
|||
tree_l=[] |
|||
## this is a no-op... |
|||
for k in range( objnames_l.__len__() ): |
|||
facek_l.append([-1, -1]) |
|||
else: |
|||
facek_l=[] |
|||
## Search face link to the ith edge |
|||
for i in range( facek.Shape.Edges.__len__() ): |
|||
#vki0 = facekEdges[i].Curve.StartPoint |
|||
if False and type(facek.Shape.Edges[i].Curve).__name__ != 'GeomLineSegment': |
|||
#vki1 = facekEdges[i].Curve.EndPoint |
|||
vki0, vki1 = self.getEndPoints(facekEdges[i]) |
|||
else: |
|||
found = False |
|||
# |
|||
for l in range(k+1, len(objnames_l)): |
|||
# Search face link to the ith edge |
|||
facel = objnames_l[l][0] |
|||
# |
|||
facelEdges = facel.Shape.Edges |
|||
for j in range(len(facelEdges)): |
|||
vki1=facek.Shape.Edges[i].Curve.EndPoint |
|||
#vlj0 = facelEdges[j].Curve.StartPoint |
|||
found=False |
|||
#vlj1 = facelEdges[j].Curve.EndPoint |
|||
for l in range( k+1 , objnames_l.__len__() ): |
|||
vlj0, vlj1 = self.getEndPoints(facelEdges[j]) |
|||
if (vki0.isEqual(vlj0, self.LIMIT) |
|||
for j in range( facel.Shape.Edges.__len__() ): |
|||
and vki1.isEqual(vlj1, self.LIMIT)): |
|||
vlj0=facel.Shape.Edges[j].Curve.StartPoint |
|||
arelinked = False |
|||
vlj1=facel.Shape.Edges[j].Curve.EndPoint |
|||
isfacek = isfacel = False |
|||
if vki0.__eq__(vlj0) and vki1.__eq__(vlj1): |
|||
for kk in range(k-1): |
|||
for ii in range(len(tree_l[kk])): |
|||
isfacek = tree_l[kk][ii][0] == k |
|||
isfacel = tree_l[kk][ii][0] == l |
|||
if isfacek and isfacel: |
|||
arelinked = True |
|||
break |
|||
if not arelinked: |
|||
facek_l.append([l, j]) |
|||
found = True |
|||
break |
|||
if not arelinked: |
|||
if found: |
|||
break |
|||
found=True |
|||
if not found: |
|||
break |
|||
facek_l.append([-1, -1]) |
|||
tree_l.append(facek_l) |
|||
return tree_l |
|||
return tree_l |
|||
def sew(self, objnames_l, tree_l): |
|||
placed_l = [] |
|||
for k in range(len(tree_l)): |
|||
iskplaced = False |
|||
for p in range(len(placed_l)): |
|||
iskplaced = placed_l[p] == k |
|||
if not iskplaced: |
|||
placed_l.append(k) |
|||
facek = tree_l[k] |
|||
objk = objnames_l[k][0] |
|||
for i in range(len(facek)): |
|||
edgeki = facek[i] |
|||
l, j = edgeki[:2] |
|||
islplaced = False |
|||
for p in range(len(placed_l)): |
|||
if placed_l[p] == l: |
|||
islplaced = True |
|||
break |
|||
if not islplaced: |
|||
placed_l.append(l) |
|||
if l >= 0 and not (islplaced and iskplaced): |
|||
iskplaced = True |
|||
## Move facel.edgelj to facek.edgeki. |
|||
objl = objnames_l[l][0] |
|||
#vki0 = objk.Shape.Edges[i].Curve.StartPoint |
|||
#vki1 = objk.Shape.Edges[i].Curve.EndPoint |
|||
vki0, vki1 = self.getEndPoints(objk.Shape.Edges[i]) |
|||
#vlj0 = objl.Shape.Edges[j].Curve.StartPoint |
|||
#vlj1 = objl.Shape.Edges[j].Curve.EndPoint |
|||
vlj0, vlj1 = self.getEndPoints(objk.Shape.Edges[j]) |
|||
vk = vki1.sub(vki0) |
|||
vl = vlj1.sub(vlj0) |
|||
alpk = vk.getAngle(vl) * 180 / math.pi |
|||
alpl = vl.getAngle(vk) * 180 / math.pi |
|||
self.isPlanZ(objk) |
|||
if islplaced: |
|||
Draft.move(objk, vlj0.sub(vki0)) |
|||
else: |
|||
Draft.move(objl, vki0.sub(vlj0)) |
|||
self.isPlanZ(objk) |
|||
if math.fabs(vk.dot(Vector(-vl.y, vl.x, 0))) > self.LIMIT: |
|||
##################################### |
|||
if islplaced: |
|||
# Function sew |
|||
Draft.rotate(objk, -alpl, vlj0, self.vecto(vl, vk)) |
|||
##################################### |
|||
else: |
|||
def sew( self,objnames_l , tree_l ): |
|||
Draft.rotate(objl, -alpk, vki0, self.vecto(vk, vl)) |
|||
placed_l=[] |
|||
elif vk.dot(vl) < 0: |
|||
if islplaced: |
|||
iskplaced=False |
|||
Draft.rotate(objk, 180, vlj0, self.vecto(vl, Vector(-vl.y, vl.x, 0))) |
|||
for p in range( placed_l.__len__() ): |
|||
else: |
|||
Draft.rotate(objl, 180, vki0, self.vecto(vk, Vector(-vk.y, vk.x, 0))) |
|||
iskplaced=True |
|||
## Verifications. |
|||
if not iskplaced:placed_l.append(k) |
|||
#vki0 = objk.Shape.Edges[i].Curve.StartPoint |
|||
facek=tree_l[k] |
|||
#vki1 = objk.Shape.Edges[i].Curve.EndPoint |
|||
objk=objnames_l[k][0] |
|||
vki0, vki1 = self.getEndPoints(objk.Shape.Edges[i]) |
|||
for i in range( facek.__len__() ): |
|||
#vlj0 = objl.Shape.Edges[j].Curve.StartPoint |
|||
edgeki=facek[i] |
|||
#vlj1 = objl.Shape.Edges[j].Curve.EndPoint |
|||
l=edgeki[0] |
|||
vli0, vli1 = self.getEndPoints(objk.Shape.Edges[j]) |
|||
j=edgeki[1] |
|||
vk = vki1.sub(vki0) |
|||
islplaced=False |
|||
vl = vlj1.sub(vlj0) |
|||
for p in range( placed_l.__len__() ): |
|||
self.isPlanZ(objk) |
|||
if placed_l[p] == l: |
|||
islplaced=True |
|||
break |
|||
if not islplaced: placed_l.append(l) |
|||
if l >= 0 and not ( islplaced and iskplaced ): |
|||
iskplaced=True |
|||
# |
|||
# Move facel.edgelj to facek.edgeki |
|||
# |
|||
objl=objnames_l[l][0] |
|||
vki0=objk.Shape.Edges[i].Curve.StartPoint |
|||
vki1=objk.Shape.Edges[i].Curve.EndPoint |
|||
vlj0=objl.Shape.Edges[j].Curve.StartPoint |
|||
vlj1=objl.Shape.Edges[j].Curve.EndPoint |
|||
vk=vki1.sub(vki0) |
|||
vl=vlj1.sub(vlj0) |
|||
alpk=vk.getAngle(vl)*180/math.pi |
|||
alpl=vl.getAngle(vk)*180/math.pi |
|||
self.isPlanZ(objk) |
|||
if islplaced: |
|||
Draft.move( objk , vlj0.sub(vki0) ) |
|||
else: |
|||
Draft.move( objl , vki0.sub(vlj0) ) |
|||
self.isPlanZ(objk) |
|||
## Flip or not. |
|||
if math.fabs( vk.dot(FreeCAD.Base.Vector(-vl.y,vl.x,0))) > self.LIMIT: |
|||
bbl, bbk = objl.Shape.BoundBox, objk.Shape.BoundBox |
|||
if islplaced: |
|||
L = max(bbl.XMax, bbk.XMax) - min(bbl.XMin, bbk.XMin) |
|||
W = max(bbl.YMax, bbk.YMax) - min(bbl.YMin, bbk.YMin) |
|||
else: |
|||
S1 = L * W |
|||
if islplaced: |
|||
elif vk.dot(vl)<0: |
|||
Draft.rotate(objk, 180, vlj0, vl) |
|||
if islplaced: |
|||
else: |
|||
Draft.rotate( objk , 180 , vlj0 , self.vecto( vl , FreeCAD.Base.Vector(-vl.y,vl.x,0) )) |
|||
Draft.rotate(objl, 180, vki0, vk) |
|||
else: |
|||
bbl, bbk = objl.Shape.BoundBox, objk.Shape.BoundBox |
|||
L = max(bbl.XMax, bbk.XMax) - min(bbl.XMin, bbk.XMin) |
|||
# |
|||
W = max(bbl.YMax, bbk.YMax) - min(bbl.YMin, bbk.YMin) |
|||
# Verifications |
|||
S2 = L * W |
|||
# |
|||
if S2 <= S1: |
|||
vki0=objk.Shape.Edges[i].Curve.StartPoint |
|||
if islplaced: |
|||
Draft.rotate(objk, 180, vlj0, vl) |
|||
vlj0=objl.Shape.Edges[j].Curve.StartPoint |
|||
else: |
|||
vlj1=objl.Shape.Edges[j].Curve.EndPoint |
|||
Draft.rotate(objl, 180, vki0, vk) |
|||
vk=vki1.sub(vki0) |
|||
self.isPlanZ(objk) |
|||
vl=vlj1.sub(vlj0) |
|||
self.isPlanZ(objk) |
|||
def isPlanZ(self, obj): |
|||
# |
|||
bb = obj.Shape.BoundBox |
|||
# Flip or not |
|||
L = bb.XMax - bb.XMin |
|||
# |
|||
W = bb.YMax - bb.YMin |
|||
L=max(objl.Shape.BoundBox.XMax,objk.Shape.BoundBox.XMax) - min( objl.Shape.BoundBox.XMin,objk.Shape.BoundBox.XMin) |
|||
H = bb.ZMax - bb.ZMin |
|||
W=max(objl.Shape.BoundBox.YMax,objk.Shape.BoundBox.YMax) - min( objl.Shape.BoundBox.YMin,objk.Shape.BoundBox.YMin) |
|||
return H < self.LIMIT |
|||
S1=L*W |
|||
if islplaced: |
|||
dum=0 |
|||
Draft.rotate( objk , 180 , vlj0 ,vl) |
|||
else: |
|||
dum=0 |
|||
Draft.rotate( objl , 180 , vki0 ,vk) |
|||
L=max(objl.Shape.BoundBox.XMax,objk.Shape.BoundBox.XMax) - min( objl.Shape.BoundBox.XMin,objk.Shape.BoundBox.XMin) |
|||
W=max(objl.Shape.BoundBox.YMax,objk.Shape.BoundBox.YMax) - min( objl.Shape.BoundBox.YMin,objk.Shape.BoundBox.YMin) |
|||
S2=L*W |
|||
if (S2<=S1): |
|||
if islplaced: |
|||
dum=0 |
|||
Draft.rotate( objk , 180 , vlj0 ,vl) |
|||
else: |
|||
dum=0 |
|||
Draft.rotate( objl , 180 , vki0 ,vk) |
|||
self.isPlanZ(objk) |
|||
##################################### |
|||
# Function isPlanZ |
|||
##################################### |
|||
def isPlanZ(self,obj): |
|||
L=obj.Shape.BoundBox.XMax - obj.Shape.BoundBox.XMin |
|||
W=obj.Shape.BoundBox.YMax - obj.Shape.BoundBox.YMin |
|||
H=obj.Shape.BoundBox.ZMax - obj.Shape.BoundBox.ZMin |
|||
if H < self.LIMIT: |
|||
return True |
|||
else: |
|||
return False |
|||
def fusion(self, objnames_l): |
|||
## Init. |
|||
obj_l, objna_l =[], [] |
|||
obj0, name = objnames_l[0] |
|||
objfuse = self.doc.addObject('Part::MultiFuse', 'Unfolding') |
|||
for k in range(len(objnames_l)): |
|||
objk = objnames_l[k][0] |
|||
obj_l.append(objk) |
|||
objfuse.Shapes = obj_l |
|||
##################################### |
|||
self.doc.recompute() |
|||
# Function fusion |
|||
objna_l.append([objfuse, name]) |
|||
##################################### |
|||
return objna_l |
|||
def fusion(self,objnames_l): |
|||
# |
|||
# Init |
|||
# |
|||
obj_l=[] |
|||
objna_l=[] |
|||
obj0=objnames_l[0][0];name=objnames_l[0][1] |
|||
objfuse=FreeCAD.activeDocument().addObject("Part::MultiFuse","Unfolding") |
|||
for k in range( objnames_l.__len__() ): |
|||
objk=objnames_l[k][0] |
|||
obj_l.append(objk) |
|||
objfuse.Shapes=obj_l |
|||
FreeCAD.activeDocument().recompute() |
|||
objna_l.append([objfuse,name]) |
|||
return objna_l |
|||
def get2Vectors(self, shape): |
|||
##################################### |
|||
"""not used in v1.1""" |
|||
# Function get2Vectors |
|||
v0, v1 = Vector(), Vector() |
|||
##################################### |
|||
def get2Vectors(self,shape): |
|||
v0=FreeCAD.Base.Vector(0,0,0) |
|||
v1=FreeCAD.Base.Vector(0,0,0) |
|||
edges= shape.Edges |
edges = shape.Edges |
||
for |
for idx in range(len(edges) - 1): |
||
e1, e2 = edges[idx], edges[idx + 1] |
|||
## .EndPoint errors out... |
|||
va = e1.Curve.EndPoint.sub(e1.Curve.StartPoint) |
|||
vb = e2.Curve.EndPoint.sub(e2.Curve.StartPoint) |
|||
v0=self.vect_copy(va);v1=self.vect_copy(vb) |
|||
if vb.sub(va).Length > v1.sub(v0).Length: |
|||
#FreeCAD.Console.PrintMessage("unfoldBox.get2Vectors: v0= {:s}, v1= {:s}\n".format(str(v0),str(v1))) |
|||
v0, v1 = self.vect_copy(va), self.vect_copy(vb) |
|||
return [ v0 , v1 ] |
|||
return [v0, v1] |
|||
def vecto(self, vect1, vect2): |
|||
##################################### |
|||
'''Function vecto.''' |
|||
v = Vector() |
|||
v.x = vect1.y * vect2.z - vect1.z * vect2.y |
|||
# - return abs(sin) angle between |
|||
v.y = vect1.z * vect2.x - vect1.x * vect2.z |
|||
# 2 vectors |
|||
v.z = vect1.x * vect2.y - vect1.y * vect2.x |
|||
##################################### |
|||
return v |
|||
def vecto( 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 |
|||
return v |
|||
def vect_copy(self, vect): |
|||
'''Return a copy of vector.''' |
|||
return vect.add(Vector()) |
|||
def moveXY(self, obj): |
|||
##################################### |
|||
## Move to origin |
|||
# Function vect_copy |
|||
bb = obj.Shape.BoundBox |
|||
Draft.move(obj, Vector(-bb.XMin, -bb.YMin, -bb.ZMin)) |
|||
# - return copy of vector |
|||
##################################### |
|||
def vect_copy( self,vect): |
|||
v= vect.add( FreeCAD.Base.Vector(0,0,0) ) |
|||
return v |
|||
## Find 2 vectors defining the plan of surface |
|||
#tab = self.get2Vectors(obj.Shape) |
|||
#v0, v1 = tab[:2] |
|||
#norm = self.vecto(v0, v1) |
|||
#norm.normalize() |
|||
## above gives null vector, and errors out... |
|||
## probably sprung out of some api-change over time |
|||
##################################### |
|||
## probably part.line vs part.linesegment... |
|||
# Function movexy |
|||
## a part.line does not have edge.Curve.StartPoint, or .EndPoint any more |
|||
##################################### |
|||
## the semantics is to get first & second point from a line, |
|||
def moveXY( self,obj ): |
|||
## so edge.Curve.value(edge.Curve.FirstParameter) should get the sought values |
|||
# |
|||
# |
## however easier to just use edge.Vertexes... |
||
## which appears to have same orientation |
|||
# |
|||
Draft.move( obj , FreeCAD.Base.Vector( -obj.Shape.BoundBox.XMin , -obj.Shape.BoundBox.YMin , -obj.Shape.BoundBox.ZMin )) |
|||
# |
|||
# Find 2 vectors defining the plan of surface |
|||
# |
|||
tab=self.get2Vectors( obj.Shape ) |
|||
v0=tab[0];v1=tab[1] |
|||
norm=self.vecto(v0,v1) |
|||
norm.normalize() |
|||
#FreeCAD.Console.PrintMessage("unfoldBox.moveXY: norm= {:s}\n".format(str(norm))) |
|||
norm = obj.Shape.findPlane().Axis ## works for planar surfaces |
|||
# |
|||
#norm = obj.Shape.Surface.Axis ## gives worse results... |
|||
# Rotate |
|||
norm.normalize() |
|||
if math.fabs(norm.x) < self.LIMIT and math.fabs(norm.y) < self.LIMIT: |
|||
dum=0 |
|||
elif math.fabs(norm.x) < self.LIMIT and math.fabs(norm.z) < self.LIMIT: |
|||
Draft.rotate( obj , 90 , FreeCAD.Base.Vector(0,0,0) , FreeCAD.Base.Vector(1,0,0) ) |
|||
elif math.fabs(norm.y) < self.LIMIT and math.fabs(norm.z) < self.LIMIT: |
|||
Draft.rotate( obj , 90 , FreeCAD.Base.Vector(0,0,0) , FreeCAD.Base.Vector(0,1,0) ) |
|||
else: |
|||
# |
|||
# Rotate following the angle to the normal direction of the plan |
|||
# |
|||
oz= FreeCAD.Base.Vector(0,0,1) |
|||
alp=oz.getAngle(norm)*180/math.pi |
|||
#FreeCAD.Console.PrintMessage("unfoldBox.moveXY: alp= "+str(alp)+"\n") |
|||
#FreeCAD.Console.PrintMessage("unfoldBox.moveXY: vecto= {:s}\n".format(str(self.vecto(oz,norm)))) |
|||
Draft.rotate( obj , -alp , FreeCAD.Base.Vector(0,0,0) , self.vecto( oz, norm )) |
|||
# |
|||
# Move to z=0 |
|||
# |
|||
Draft.move( obj , FreeCAD.Base.Vector( 0 , 0 , -obj.Shape.BoundBox.ZMin )) |
|||
L=obj.Shape.BoundBox.XMax - obj.Shape.BoundBox.XMin |
|||
W=obj.Shape.BoundBox.YMax - obj.Shape.BoundBox.YMin |
|||
H=obj.Shape.BoundBox.ZMax - obj.Shape.BoundBox.ZMin |
|||
## Rotate. |
|||
nx, ny, nz = (math.fabs(xyz) for xyz in norm) |
|||
if nx < self.LIMIT and nz < self.LIMIT: |
|||
Draft.rotate(obj, 90, Vector(0, 0, 0), Vector(1, 0, 0)) |
|||
elif ny < self.LIMIT and nz < self.LIMIT: |
|||
Draft.rotate(obj, 90, Vector(0, 0, 0), Vector(0, 1, 0)) |
|||
else: |
|||
## Rotate following the angle to the normal direction of the plan. |
|||
oz = Vector(0, 0, 1) |
|||
alp = oz.getAngle(norm) * 180 / math.pi |
|||
vecto = self.vecto(oz, norm) |
|||
rotv = oz if vecto.isEqual(Vector(), self.LIMIT) else self.vecto(oz, norm) |
|||
#rotv = norm if vecto.isEqual(Vector(), self.LIMIT) else self.vecto(oz, norm) |
|||
## flipping here seems to give worse results as well... |
|||
Draft.rotate(obj, -alp, Vector(0, 0, 0), rotv) |
|||
## Move to z = 0. |
|||
##################################### |
|||
Draft.move(obj, Vector(0, 0, -obj.Shape.BoundBox.ZMin)) |
|||
# Class Drawing2d |
|||
##################################### |
|||
class Drawing2d: |
|||
##################################### |
|||
# Function __init__ |
|||
# - Scale |
|||
# - scale_auto |
|||
# - a3 |
|||
# - cartridge |
|||
# - onedrawing |
|||
##################################### |
|||
def __init__( self, scale , scale_auto , a3 , cartridge , onedrawing , drawing_name , page_name ): |
|||
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.pts_nbr=100 |
|||
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.page_name=page_name |
|||
self.drawing_name=drawing_name |
|||
##################################### |
|||
class Scale: |
|||
# Function newPage |
|||
"""keeps autoscaling to integers""" |
|||
##################################### |
|||
def |
def __init__(self, scale): |
||
self.scale = scale if scale >= 1 else 1 / scale |
|||
freecad_dir=os.getenv('HOME')+"/.FreeCAD/Mod/unfoldBox" |
|||
self.scale = int(self.scale) |
|||
page = FreeCAD.activeDocument().addObject('Drawing::FeaturePage', self.page_name ) |
|||
self.inverted = scale >= 1 |
|||
if self.cartridge: |
|||
def get(self): |
|||
page.Template = freecad_dir+'/A3_Landscape.svg' |
|||
if self.inverted: |
|||
return self.scale, '{}:1'.format(self.scale) |
|||
else: |
else: |
||
return 1/self.scale, '1:{}'.format(self.scale) |
|||
page.Template = freecad_dir+'/A3_Landscape_Empty.svg' |
|||
else: |
|||
if self.cartridge: |
|||
class Drawing2d: |
|||
page.Template = freecad_dir+'/A4_Landscape.svg' |
|||
def __init__(self, scale, scale_auto, a3, cartridge): |
|||
"""techdraw based layout for one page only""" |
|||
self.scale = scale |
|||
self.scale_auto = scale_auto |
|||
self.a3 = a3 |
|||
self.cartridge = cartridge |
|||
if a3: |
|||
self.WH = 420, 297 |
|||
else: |
else: |
||
self.WH = 297, 210 |
|||
self.doc = FreeCAD.ActiveDocument |
|||
return page |
|||
def newPage(self, page_name): |
|||
##################################### |
|||
freecad_dir = os.path.join(FreeCAD.getResourceDir(), dwgtpl) |
|||
# Function all |
|||
page = self.doc.addObject('TechDraw::DrawPage', page_name) |
|||
##################################### |
|||
template = self.doc.addObject('TechDraw::DrawSVGTemplate', 'Template') |
|||
def all( self, objnames_l ): |
|||
size = 'A3' if self.a3 else 'A4' |
|||
obj_l=[] |
|||
frame = 'TD' if self.cartridge else '_blank' |
|||
for objid in range( objnames_l.__len__() ): |
|||
template.Template = freecad_dir + '/{}_Landscape{}.svg'.format(size, frame) |
|||
if objid == 0 or not self.onedrawing: |
|||
page.Template = template |
|||
obj_l.extend( self.done( objid , objnames_l[objid] )) |
|||
return obj_l |
|||
return page |
|||
def drawpage(self, objname, page_name): |
|||
page = self.newPage(page_name) |
|||
faceset, name = objname |
|||
bb = faceset.Shape.BoundBox |
|||
## bb does not auto-update, have to pick up new bb after manipulation |
|||
if bb.YLength > bb.XLength: ## auto rotate |
|||
Draft.rotate(faceset, 90) |
|||
bb = faceset.Shape.BoundBox |
|||
Draft.move(faceset, Vector(-bb.XMin, -bb.YMin, 0)) |
|||
##################################### |
|||
bb = faceset.Shape.BoundBox |
|||
# Function done |
|||
##################################### |
|||
def done( self, id , objname ): |
|||
# |
|||
# Init |
|||
# |
|||
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 |
|||
W, H = self.WH |
|||
scale=min((self.L-4*self.marge)/xmax,(self.H-4*self.marge)/ymax) |
|||
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) ## faceset in xy-plane |
|||
if ( not self.scale_auto ) or ( self.onedrawing ) : |
|||
scale=self.scale |
|||
TopView = self.doc.addObject('TechDraw::DrawViewPart', 'TopView') |
|||
page.addView(TopView) |
|||
TopView.Source = faceset |
|||
TopView.Direction = (0, 0, 1) |
|||
TopView.XDirection = (1, 0, 0) |
|||
TopView.Scale = scale |
|||
Text = self.doc.addObject('TechDraw::DrawViewAnnotation', name) |
|||
if id == 0 or not self.onedrawing: |
|||
page.addView(Text) |
|||
Text.Text = name |
|||
Text.recompute() # for size |
|||
# |
|||
bb.scale(scale, scale, scale) |
|||
FreeCAD.Console.PrintMessage("Dawing2d: init\n") |
|||
yt = (H - bb.YLength) / 2 - Text.TextSize.Value * 1.5 |
|||
self.TopX_H=self.marge*2 |
|||
Text.Y = max(1, yt) |
|||
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.BoundBox.XMin , -obj.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: |
|||
obj_l.append( [ obj , name ] ) |
|||
return obj_l |
|||
self.doc.recompute() |
|||
page.ViewObject.doubleClicked() |
|||
# |
|||
FreeCADGui.runCommand('TechDraw_ToggleFrame', 0) |
|||
# 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.BoundBox.XMin , -obj.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 ) |
|||
else: |
|||
obj_l.append( [ obj , name ] ) |
|||
return obj_l |
|||
page=FreeCAD.activeDocument().getObject(self.page_name ) |
|||
class Drawing2dLegacy: |
|||
Text=FreeCAD.activeDocument().addObject('Drawing::FeatureViewAnnotation', objname+"_txt") |
|||
def __init__(self, scale, scale_auto, a3, cartridge, onedrawing, |
|||
Text.Text=objname |
|||
drawing_name, page_name): |
|||
Text.X=TopX+xmax/2*scale |
|||
"""Function __init__ |
|||
Text.Y=TopY+ymax/2*scale |
|||
Text.Scale=1 |
|||
- scale |
|||
TopView = FreeCAD.activeDocument().addObject('Drawing::FeatureViewPart','TopView') |
|||
- scale_auto |
|||
TopView.Source = obj |
|||
- a3 |
|||
TopView.Direction = (0.0,0.0,1) |
|||
- cartridge |
|||
TopView.Rotation = 0 |
|||
- onedrawing |
|||
v1.1 - renamed to legacy, functional wise untouched |
|||
TopView.Y = TopY |
|||
""" |
|||
TopView.ShowHiddenLines = True |
|||
self.TopX_H = self.TopY_H = 0 |
|||
self.TopX_V = self.TopY_V = 0 |
|||
page.addObject(TopView) |
|||
self.TopX_Hmax = self.TopY_Hmax = 0 |
|||
page.addObject(Text) |
|||
self.TopX_Vmax = self.TopY_Vmax = 0 |
|||
FreeCAD.activeDocument().recompute() |
|||
self.a3 = a3 |
|||
return obj_l |
|||
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.page_name = page_name |
|||
self.drawing_name = drawing_name |
|||
self.doc = FreeCAD.ActiveDocument |
|||
def newPage(self): |
|||
freecad_dir = os.path.join(FreeCAD.getResourceDir(), dwgtpllegacy) |
|||
page = self.doc.addObject('Drawing::FeaturePage', self.page_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 all_(self, objnames_l): |
|||
obj_l = [] |
|||
for objid in range(len(objnames_l)): |
|||
if objid == 0 or not self.onedrawing: |
|||
self.newPage() |
|||
obj_l.extend(self.done(objid, objnames_l[objid])) |
|||
return obj_l |
|||
def done(self, id_, objname): |
|||
## Init. |
|||
obj_l = [] |
|||
obj, objname = objname |
|||
bb = obj.Shape.BoundBox |
|||
xmax, ymax = bb.XMax - bb.XMin, bb.YMax - bb.YMin |
|||
if ymax > xmax: |
|||
Draft.rotate(obj, 90) |
|||
bb = obj.Shape.BoundBox |
|||
Draft.move(obj, Vector(-bb.XMin, -bb.YMin, 0)) |
|||
bb = obj.Shape.BoundBox ## does not auto-update, have to pick up new obj |
|||
xmax = bb.XMax - bb.XMin |
|||
ymax = bb.YMax - bb.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 |
|||
if id_ == 0 or not self.onedrawing: |
|||
## Init. |
|||
PrintMessage('Drawing2d: init\n') |
|||
self.TopX_H = self.TopY_H = self.marge * 2 |
|||
TopX, TopY = self.TopX_H, 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. |
|||
PrintMessage('Drawing2d: horizontal\n') |
|||
TopX, TopY = self.TopX_H, 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 |
|||
PrintMessage('Drawing2d: 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) |
|||
bb = obj.Shape.BoundBox |
|||
Draft.move(obj, Vector(-bb.XMin, -bb.YMin, 0)) |
|||
self.TopX_V = max(self.TopX_Vmax, self.TopX_V) |
|||
TopX, TopY = self.TopX_V, 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: |
|||
obj_l.append([obj, '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: vertical: {}, {}\n' |
|||
PrintMessage(msg.format(self.TopX_V, self.TopX_Vmax)) |
|||
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) |
|||
bb = obj.Shape.BoundBox |
|||
Draft.move(obj, Vector(-bb.XMin, -bb.YMin, 0)) |
|||
TopX, TopY = self.TopX_V, 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: |
|||
obj_l.append([obj, 'name']) |
|||
return obj_l |
|||
page = self.doc.getObject(self.page_name) |
|||
## Drawing wb (untouched) |
|||
Text = self.doc.addObject('Drawing::FeatureViewAnnotation', objname + '_txt') |
|||
Text.Text = objname |
|||
Text.X = TopX + xmax / 2 * scale |
|||
Text.Y = TopY + ymax / 2 * scale |
|||
Text.Scale = 7 if self.a3 else 5 |
|||
TopView = self.doc.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 = True |
|||
TopView.Scale = scale |
|||
page.addObject(TopView) |
|||
page.addObject(Text) |
|||
self.doc.recompute() |
|||
page.ViewObject.doubleClicked() |
|||
return obj_l |
|||
##################################### |
##################################### |
||
### Dialog Box |
|||
##################################### |
##################################### |
||
fields = [['Group Name', 'Unfolding']] |
|||
# Dialog Box |
|||
fields.append(['Scale', '1']) |
|||
##################################### |
|||
##################################### |
|||
fields = [[ "Group Name" , "Unfolding" ]] |
|||
fields.append(["Scale","1" ]) |
|||
DialogBox = QtGui.QDialog() |
DialogBox = QtGui.QDialog() |
||
DialogBox.resize(250,250) |
DialogBox.resize(250, 250) |
||
DialogBox.setWindowTitle( |
DialogBox.setWindowTitle('unfoldBox') |
||
la = QtGui.QVBoxLayout(DialogBox) |
la = QtGui.QVBoxLayout(DialogBox) |
||
# Input fields. |
|||
# |
|||
for id_ in range(len(fields)): |
|||
la.addWidget(QtGui.QLabel(fields[id_][0])) |
|||
# |
|||
fields_l.append(QtGui.QLineEdit(fields[id_][1])) |
|||
la.addWidget( |
la.addWidget(fields_l[id_]) |
||
fields_l.append( QtGui.QLineEdit( fields[ id ][ 1 ] )) |
|||
makedwg_check = QtGui.QCheckBox(DialogBox) |
|||
la.addWidget( fields_l[ id ] ) |
|||
makedwg_check.setObjectName('checkBox') |
|||
makedwg_check.setChecked(True) |
|||
la.addWidget(QtGui.QLabel('Make drawing')) |
|||
la.addWidget(makedwg_check) |
|||
leglay_check = QtGui.QCheckBox(DialogBox) |
|||
leglay_check.setObjectName('checkBox') |
|||
leglay_check.setChecked(False) |
|||
la.addWidget(QtGui.QLabel('Legacy layout')) |
|||
la.addWidget(leglay_check) |
|||
scale_check = QtGui.QCheckBox( |
scale_check = QtGui.QCheckBox(DialogBox) |
||
scale_check.setObjectName( |
scale_check.setObjectName('checkBox') |
||
scale_check.setChecked(True) |
scale_check.setChecked(True) |
||
la.addWidget(QtGui.QLabel( |
la.addWidget(QtGui.QLabel('Scale auto')) |
||
la.addWidget(scale_check) |
la.addWidget(scale_check) |
||
a3_check = QtGui.QCheckBox( |
a3_check = QtGui.QCheckBox(DialogBox) |
||
a3_check.setObjectName( |
a3_check.setObjectName('checkBox') |
||
la.addWidget(QtGui.QLabel( |
la.addWidget(QtGui.QLabel('A3 Format')) |
||
a3_check.setChecked(False) |
a3_check.setChecked(False) |
||
la.addWidget(a3_check) |
la.addWidget(a3_check) |
||
cartridge_check = QtGui.QCheckBox( |
cartridge_check = QtGui.QCheckBox(DialogBox) |
||
cartridge_check.setObjectName( |
cartridge_check.setObjectName('checkBox') |
||
la.addWidget(QtGui.QLabel( |
la.addWidget(QtGui.QLabel('Cartridge')) |
||
cartridge_check.setChecked(False) |
cartridge_check.setChecked(False) |
||
la.addWidget(cartridge_check) |
la.addWidget(cartridge_check) |
||
onedrawing_check = QtGui.QCheckBox( |
onedrawing_check = QtGui.QCheckBox(DialogBox) |
||
onedrawing_check.setObjectName( |
onedrawing_check.setObjectName('checkBox') |
||
la.addWidget(QtGui.QLabel( |
la.addWidget(QtGui.QLabel('Group drawings in page')) |
||
onedrawing_check.setChecked(True) |
onedrawing_check.setChecked(True) |
||
la.addWidget(onedrawing_check) |
la.addWidget(onedrawing_check) |
||
sewed_check = QtGui.QCheckBox( |
sewed_check = QtGui.QCheckBox(DialogBox) |
||
sewed_check.setObjectName( |
sewed_check.setObjectName('checkBox') |
||
la.addWidget(QtGui.QLabel( |
la.addWidget(QtGui.QLabel('Sewed surfaces')) |
||
sewed_check.setChecked(True) |
sewed_check.setChecked(True) |
||
sewed_check.setEnabled(False) |
|||
la.addWidget(sewed_check) |
la.addWidget(sewed_check) |
||
Line 672: | Line 821: | ||
box = QtGui.QDialogButtonBox(DialogBox) |
box = QtGui.QDialogButtonBox(DialogBox) |
||
box.setOrientation(QtCore.Qt.Horizontal) |
box.setOrientation(QtCore.Qt.Horizontal) |
||
box.setStandardButtons(QtGui.QDialogButtonBox.Cancel|QtGui.QDialogButtonBox.Ok) |
box.setStandardButtons(QtGui.QDialogButtonBox.Cancel | QtGui.QDialogButtonBox.Ok) |
||
la.addWidget(box) |
la.addWidget(box) |
||
QtCore.QObject.connect(box, QtCore.SIGNAL( |
QtCore.QObject.connect(box, QtCore.SIGNAL('accepted()'), proceed) |
||
QtCore.QObject.connect(box, QtCore.SIGNAL( |
QtCore.QObject.connect(box, QtCore.SIGNAL('rejected()'), close) |
||
QtCore.QMetaObject.connectSlotsByName(DialogBox) |
QtCore.QMetaObject.connectSlotsByName(DialogBox) |
||
DialogBox.show() |
DialogBox.show() |
||
</pre> |
|||
</syntaxhighlight> |
|||
{{clear}} |
|||
<languages/> |
Latest revision as of 18:55, 30 July 2022
Popis |
---|
Toto makro rozloží povrchy krabice jakéhokoliv tvaru a nakreslí je na stránku. This macro must be upgraded cause error indentation and space mixed with tabulation Version macro : 1.0 Date last modification : 2013-09-20 Download : ToolBar Icon Autor: Hervé B. |
Autor |
Hervé B. |
Download |
ToolBar Icon |
Odkazy |
Makro recepty| Jak nainstalovat makra Jak přizpůsobit panely nástrojů |
Verze |
1.0 |
Datum poslední úpravy |
2013-09-20 |
Verze FreeCAD |
Výchozí zástupce |
None |
Viz též |
None |
Description
Deskriptivní
Toto makro rozloží povrchy krabice jakéhokoliv tvaru a nakreslí je na stránku.
Installation
Instalace
Zkopírujte kód makra do souboru v adresáři :
- Linux & Mac : $home/.Freecad/Mod/unfoldBox.
- Windows : C:\Program Files\FreeCAD0.13
Přidejte šablony : A3_Landscape_Empty.svg A3_Landscape.svg A4_Landscape_Empty.svg A4_Landscape.svg
Cf Makro pro rozložení povrchů krabice
Forum topic: Macro for unfolding box surfaces.
Options
Volby
- Měřítko ruční nebo automatické
- Formát stránky: a3/a4, cartridge (cf FreeCAD šablony)
- Je-li to možné seskup výkresy na stejnou stránku.
- Sešít nebo nesešít okraje kousků.
Usage
Použití
- Vyber krabici vytvořenou například pomocí nástroje Díl::Profilování.
- Rozděl jej (cf Draft menu) do nárysů
- Vyber povrchy
- Spusť makro
The unfolding algorithm will place the faces on the XY plane, however it seldom does the unfolding correctly. Thus some manual post-processing is needed to get to desired result, as shown in the picture below.
Starting point is a box like the upper left picture.
- Select all faces and run the macro.
- Orient to Top View.
- Toggle the visibility on the created set of faces, the individual clones are to be visible, not the compound.
- Use Draft Move and Draft Rotate to reposition the unfolded cloned faces (a handful of clicks per face when snapping is used).
- The drawing updates automatically to the new positions.
The finished result is shown in the lower right picture above.
Skript
Macro_unfoldBox.FCMacro
# -*- coding: utf-8 -*- """ FreeCAD Macro unfoldBox. Unfolding of planar surfaces """ ################################################## # SEE https://wiki.freecadweb.org/Macro_Unfold_Box # *************************************************************************** # * * # * 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__ = 'Unfold Box' __Comment__ = 'Unfolds planar surfaces and draws them on a page.' __Author__ = 'Hervé B., heda' __Version__ = '1.1' __Date__ = '2022-07-28' __License__ = 'LGPL-2.0-or-later' __Web__ = 'https://wiki.freecad.org/Macro_Unfold_Box' __Wiki__ = 'https://wiki.freecad.org/Macro_Unfold_Box' __Icon__ = '' __Help__ = ('Select surfaces, Explode them (cf Draft menu Downgrade), ' 'Select the exploded 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 unfold a set of planar faces, in current state, most of the times it does not place the faces correctly. thus one needs to do the final placement of the clones manually, with Draft/Move & Rotate regardless, the macro saves a lot of clicks compared to a fully manual unfold settings are not context aware, all settings not applicable are ignored. as example, using autoscaling ignores the scale-value in the form sew is always enabled, thus the drawing is always the sewed shape on a single page v1.1 (2022-07-28) py3/qt5 compat, cosmetic code changes, minor code tweaks for evolved fc-api added option to skip drawing, made layout engine with techdraw, the drawing wb layout engine is now called "legacy" v1.0.1 (2020-03-10) - on wiki v1.0 (2013-09-14) - on wiki note: - unfolding occasionally work as expected, but mostly not - anyone is free to improve functionality """ import os, math from PySide import QtGui, QtCore import FreeCAD, FreeCADGui import Draft Vector = FreeCAD.Base.Vector Units = FreeCAD.Units PrintMessage = FreeCAD.Console.PrintMessage PrintError = FreeCAD.Console.PrintError fields_l = [] 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 proceed(): QtGui.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor) PrintMessage('===========================================\n') PrintMessage('unfoldBox: start.\n') try: file_name = fields_l[0].text() makedwg = makedwg_check.isChecked() leglay = leglay_check.isChecked() scale = float(fields_l[1].text()) scale_auto = scale_check.isChecked() a3 = a3_check.isChecked() cartridge = cartridge_check.isChecked() onedrawing = onedrawing_check.isChecked() sewed = sewed_check.isChecked() PrintMessage('unfoldBox.file_name: {}\n'.format(file_name)) PrintMessage('unfoldBox.makedwg: {}\n'.format(makedwg)) PrintMessage('unfoldBox.leglay: {}\n'.format(leglay)) PrintMessage('unfoldBox.scale: {}\n'.format(scale)) PrintMessage('unfoldBox.scale_check: {}\n'.format(scale_auto)) PrintMessage('unfoldBox.a3_check: {}\n'.format(a3)) PrintMessage('unfoldBox.cartridge: {}\n'.format(cartridge)) PrintMessage('unfoldBox.onedrawing: {}\n'.format(onedrawing)) PrintMessage('unfoldBox.sewed: {}\n'.format(sewed)) except: msg = 'unfoldBox: wrong inputs...\n' PrintError(msg) errorDialog(msg) QtGui.QApplication.restoreOverrideCursor() DialogBox.hide() ## Get selection sel = FreeCADGui.Selection.getSelection() if not sel or len(sel) < 2: PrintMessage('unfoldBox: requires at least 2 selected faces, ending.\n') return doc = FreeCAD.ActiveDocument objnames_l=[] grp = doc.addObject('App::DocumentObjectGroup', str(file_name)) for objid in range(len(sel)): obj = Draft.make_clone(sel[objid]) grp.addObject(obj) objnames_l.append([ obj, sel[objid].Name ]) doc.recompute() unfold = unfoldBox(doc) if sewed: objnames_l = unfold.done(objnames_l) grp.addObject(objnames_l[0][0]) else: for objid in range(len(objnames_l)): unfold.moveXY(objnames_l[objid][0]) if not makedwg: return if leglay: idx = 0 while len(objnames_l) > 0: draw = Drawing2dLegacy(scale, scale_auto, a3, cartridge, onedrawing, doc.Name, 'Page'+str(idx)) objnames_l = draw.all_(objnames_l) idx += 1 PrintMessage('unfoldBox: obj_l= {}\n'.format(len(objnames_l))) else: draw = Drawing2d(scale, scale_auto, a3, cartridge) draw.drawpage(objnames_l[0], "{}Page".format(file_name)) PrintMessage('unfoldBox: end.\n') PrintMessage('===========================================\n') def close(): DialogBox.hide() def getType(obj): return type(obj).__name__ def mkmm(l): return Units.Quantity(l, Units.Length) class unfoldBox: def __init__(self, doc): PrintMessage('unfoldBox.unfoldBox\n') self.doc = doc self.LIMIT = 0.0001 def done(self, objnames_l): tree_l = self.makeTree(objnames_l) for idx in range(len(objnames_l)): face = objnames_l[idx] self.moveXY(face[0]) self.sew(objnames_l, tree_l) return self.fusion(objnames_l) def getEndPoints(self, edge): return [v.Point for v in edge.Vertexes] def makeTree(self, objnames_l): ## Initialisation of tree_l. tree_l = [] for k in range(len(objnames_l)): facek = objnames_l[k][0] facekEdges = facek.Shape.Edges facek_l = [] for i in range(len(facekEdges)): if False and getType(facekEdges[i].Curve) != 'GeomLineSegment': ## this is a no-op... facek_l.append([-1, -1]) else: ## Search face link to the ith edge #vki0 = facekEdges[i].Curve.StartPoint #vki1 = facekEdges[i].Curve.EndPoint vki0, vki1 = self.getEndPoints(facekEdges[i]) found = False for l in range(k+1, len(objnames_l)): facel = objnames_l[l][0] facelEdges = facel.Shape.Edges for j in range(len(facelEdges)): #vlj0 = facelEdges[j].Curve.StartPoint #vlj1 = facelEdges[j].Curve.EndPoint vlj0, vlj1 = self.getEndPoints(facelEdges[j]) if (vki0.isEqual(vlj0, self.LIMIT) and vki1.isEqual(vlj1, self.LIMIT)): arelinked = False isfacek = isfacel = False for kk in range(k-1): for ii in range(len(tree_l[kk])): isfacek = tree_l[kk][ii][0] == k isfacel = tree_l[kk][ii][0] == l if isfacek and isfacel: arelinked = True break if not arelinked: facek_l.append([l, j]) found = True break if found: break if not found: facek_l.append([-1, -1]) tree_l.append(facek_l) return tree_l def sew(self, objnames_l, tree_l): placed_l = [] for k in range(len(tree_l)): iskplaced = False for p in range(len(placed_l)): iskplaced = placed_l[p] == k if not iskplaced: placed_l.append(k) facek = tree_l[k] objk = objnames_l[k][0] for i in range(len(facek)): edgeki = facek[i] l, j = edgeki[:2] islplaced = False for p in range(len(placed_l)): if placed_l[p] == l: islplaced = True break if not islplaced: placed_l.append(l) if l >= 0 and not (islplaced and iskplaced): iskplaced = True ## Move facel.edgelj to facek.edgeki. objl = objnames_l[l][0] #vki0 = objk.Shape.Edges[i].Curve.StartPoint #vki1 = objk.Shape.Edges[i].Curve.EndPoint vki0, vki1 = self.getEndPoints(objk.Shape.Edges[i]) #vlj0 = objl.Shape.Edges[j].Curve.StartPoint #vlj1 = objl.Shape.Edges[j].Curve.EndPoint vlj0, vlj1 = self.getEndPoints(objk.Shape.Edges[j]) vk = vki1.sub(vki0) vl = vlj1.sub(vlj0) alpk = vk.getAngle(vl) * 180 / math.pi alpl = vl.getAngle(vk) * 180 / math.pi self.isPlanZ(objk) if islplaced: Draft.move(objk, vlj0.sub(vki0)) else: Draft.move(objl, vki0.sub(vlj0)) self.isPlanZ(objk) if math.fabs(vk.dot(Vector(-vl.y, vl.x, 0))) > self.LIMIT: if islplaced: Draft.rotate(objk, -alpl, vlj0, self.vecto(vl, vk)) else: Draft.rotate(objl, -alpk, vki0, self.vecto(vk, vl)) elif vk.dot(vl) < 0: if islplaced: Draft.rotate(objk, 180, vlj0, self.vecto(vl, Vector(-vl.y, vl.x, 0))) else: Draft.rotate(objl, 180, vki0, self.vecto(vk, Vector(-vk.y, vk.x, 0))) ## Verifications. #vki0 = objk.Shape.Edges[i].Curve.StartPoint #vki1 = objk.Shape.Edges[i].Curve.EndPoint vki0, vki1 = self.getEndPoints(objk.Shape.Edges[i]) #vlj0 = objl.Shape.Edges[j].Curve.StartPoint #vlj1 = objl.Shape.Edges[j].Curve.EndPoint vli0, vli1 = self.getEndPoints(objk.Shape.Edges[j]) vk = vki1.sub(vki0) vl = vlj1.sub(vlj0) self.isPlanZ(objk) ## Flip or not. bbl, bbk = objl.Shape.BoundBox, objk.Shape.BoundBox L = max(bbl.XMax, bbk.XMax) - min(bbl.XMin, bbk.XMin) W = max(bbl.YMax, bbk.YMax) - min(bbl.YMin, bbk.YMin) S1 = L * W if islplaced: Draft.rotate(objk, 180, vlj0, vl) else: Draft.rotate(objl, 180, vki0, vk) bbl, bbk = objl.Shape.BoundBox, objk.Shape.BoundBox L = max(bbl.XMax, bbk.XMax) - min(bbl.XMin, bbk.XMin) W = max(bbl.YMax, bbk.YMax) - min(bbl.YMin, bbk.YMin) S2 = L * W if S2 <= S1: if islplaced: Draft.rotate(objk, 180, vlj0, vl) else: Draft.rotate(objl, 180, vki0, vk) self.isPlanZ(objk) def isPlanZ(self, obj): bb = obj.Shape.BoundBox L = bb.XMax - bb.XMin W = bb.YMax - bb.YMin H = bb.ZMax - bb.ZMin return H < self.LIMIT def fusion(self, objnames_l): ## Init. obj_l, objna_l =[], [] obj0, name = objnames_l[0] objfuse = self.doc.addObject('Part::MultiFuse', 'Unfolding') for k in range(len(objnames_l)): objk = objnames_l[k][0] obj_l.append(objk) objfuse.Shapes = obj_l self.doc.recompute() objna_l.append([objfuse, name]) return objna_l def get2Vectors(self, shape): """not used in v1.1""" v0, v1 = Vector(), Vector() edges = shape.Edges for idx in range(len(edges) - 1): e1, e2 = edges[idx], edges[idx + 1] ## .EndPoint errors out... va = e1.Curve.EndPoint.sub(e1.Curve.StartPoint) vb = e2.Curve.EndPoint.sub(e2.Curve.StartPoint) if vb.sub(va).Length > v1.sub(v0).Length: v0, v1 = self.vect_copy(va), self.vect_copy(vb) return [v0, v1] def vecto(self, vect1, vect2): '''Function vecto.''' 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 def vect_copy(self, vect): '''Return a copy of vector.''' return vect.add(Vector()) def moveXY(self, obj): ## Move to origin bb = obj.Shape.BoundBox Draft.move(obj, Vector(-bb.XMin, -bb.YMin, -bb.ZMin)) ## Find 2 vectors defining the plan of surface #tab = self.get2Vectors(obj.Shape) #v0, v1 = tab[:2] #norm = self.vecto(v0, v1) #norm.normalize() ## above gives null vector, and errors out... ## probably sprung out of some api-change over time ## probably part.line vs part.linesegment... ## a part.line does not have edge.Curve.StartPoint, or .EndPoint any more ## the semantics is to get first & second point from a line, ## so edge.Curve.value(edge.Curve.FirstParameter) should get the sought values ## however easier to just use edge.Vertexes... ## which appears to have same orientation norm = obj.Shape.findPlane().Axis ## works for planar surfaces #norm = obj.Shape.Surface.Axis ## gives worse results... norm.normalize() ## Rotate. nx, ny, nz = (math.fabs(xyz) for xyz in norm) if nx < self.LIMIT and nz < self.LIMIT: Draft.rotate(obj, 90, Vector(0, 0, 0), Vector(1, 0, 0)) elif ny < self.LIMIT and nz < self.LIMIT: Draft.rotate(obj, 90, Vector(0, 0, 0), Vector(0, 1, 0)) else: ## Rotate following the angle to the normal direction of the plan. oz = Vector(0, 0, 1) alp = oz.getAngle(norm) * 180 / math.pi vecto = self.vecto(oz, norm) rotv = oz if vecto.isEqual(Vector(), self.LIMIT) else self.vecto(oz, norm) #rotv = norm if vecto.isEqual(Vector(), self.LIMIT) else self.vecto(oz, norm) ## flipping here seems to give worse results as well... Draft.rotate(obj, -alp, Vector(0, 0, 0), rotv) ## Move to z = 0. Draft.move(obj, Vector(0, 0, -obj.Shape.BoundBox.ZMin)) 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: def __init__(self, scale, scale_auto, a3, cartridge): """techdraw based layout for one page only""" self.scale = scale self.scale_auto = scale_auto self.a3 = a3 self.cartridge = cartridge if a3: self.WH = 420, 297 else: self.WH = 297, 210 self.doc = FreeCAD.ActiveDocument def newPage(self, page_name): freecad_dir = os.path.join(FreeCAD.getResourceDir(), dwgtpl) page = self.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, objname, page_name): page = self.newPage(page_name) faceset, name = objname bb = faceset.Shape.BoundBox ## bb does not auto-update, have to pick up new bb after manipulation if bb.YLength > bb.XLength: ## auto rotate Draft.rotate(faceset, 90) bb = faceset.Shape.BoundBox Draft.move(faceset, Vector(-bb.XMin, -bb.YMin, 0)) bb = faceset.Shape.BoundBox W, H = self.WH 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) ## faceset in xy-plane TopView = self.doc.addObject('TechDraw::DrawViewPart', 'TopView') page.addView(TopView) TopView.Source = faceset TopView.Direction = (0, 0, 1) TopView.XDirection = (1, 0, 0) TopView.Scale = scale Text = self.doc.addObject('TechDraw::DrawViewAnnotation', name) page.addView(Text) Text.Text = name Text.recompute() # for size bb.scale(scale, scale, scale) yt = (H - 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: def __init__(self, scale, scale_auto, a3, cartridge, onedrawing, drawing_name, page_name): """Function __init__ - scale - scale_auto - a3 - cartridge - onedrawing v1.1 - renamed to legacy, functional wise untouched """ 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.page_name = page_name self.drawing_name = drawing_name self.doc = FreeCAD.ActiveDocument def newPage(self): freecad_dir = os.path.join(FreeCAD.getResourceDir(), dwgtpllegacy) page = self.doc.addObject('Drawing::FeaturePage', self.page_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 all_(self, objnames_l): obj_l = [] for objid in range(len(objnames_l)): if objid == 0 or not self.onedrawing: self.newPage() obj_l.extend(self.done(objid, objnames_l[objid])) return obj_l def done(self, id_, objname): ## Init. obj_l = [] obj, objname = objname bb = obj.Shape.BoundBox xmax, ymax = bb.XMax - bb.XMin, bb.YMax - bb.YMin if ymax > xmax: Draft.rotate(obj, 90) bb = obj.Shape.BoundBox Draft.move(obj, Vector(-bb.XMin, -bb.YMin, 0)) bb = obj.Shape.BoundBox ## does not auto-update, have to pick up new obj xmax = bb.XMax - bb.XMin ymax = bb.YMax - bb.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 if id_ == 0 or not self.onedrawing: ## Init. PrintMessage('Drawing2d: init\n') self.TopX_H = self.TopY_H = self.marge * 2 TopX, TopY = self.TopX_H, 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. PrintMessage('Drawing2d: horizontal\n') TopX, TopY = self.TopX_H, 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 PrintMessage('Drawing2d: 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) bb = obj.Shape.BoundBox Draft.move(obj, Vector(-bb.XMin, -bb.YMin, 0)) self.TopX_V = max(self.TopX_Vmax, self.TopX_V) TopX, TopY = self.TopX_V, 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: obj_l.append([obj, '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: vertical: {}, {}\n' PrintMessage(msg.format(self.TopX_V, self.TopX_Vmax)) 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) bb = obj.Shape.BoundBox Draft.move(obj, Vector(-bb.XMin, -bb.YMin, 0)) TopX, TopY = self.TopX_V, 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: obj_l.append([obj, 'name']) return obj_l page = self.doc.getObject(self.page_name) ## Drawing wb (untouched) Text = self.doc.addObject('Drawing::FeatureViewAnnotation', objname + '_txt') Text.Text = objname Text.X = TopX + xmax / 2 * scale Text.Y = TopY + ymax / 2 * scale Text.Scale = 7 if self.a3 else 5 TopView = self.doc.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 = True TopView.Scale = scale page.addObject(TopView) page.addObject(Text) self.doc.recompute() page.ViewObject.doubleClicked() return obj_l ##################################### ### Dialog Box ##################################### fields = [['Group Name', 'Unfolding']] fields.append(['Scale', '1']) DialogBox = QtGui.QDialog() DialogBox.resize(250, 250) DialogBox.setWindowTitle('unfoldBox') la = QtGui.QVBoxLayout(DialogBox) # Input fields. for id_ in range(len(fields)): la.addWidget(QtGui.QLabel(fields[id_][0])) fields_l.append(QtGui.QLineEdit(fields[id_][1])) la.addWidget(fields_l[id_]) makedwg_check = QtGui.QCheckBox(DialogBox) makedwg_check.setObjectName('checkBox') makedwg_check.setChecked(True) la.addWidget(QtGui.QLabel('Make drawing')) la.addWidget(makedwg_check) leglay_check = QtGui.QCheckBox(DialogBox) leglay_check.setObjectName('checkBox') leglay_check.setChecked(False) la.addWidget(QtGui.QLabel('Legacy layout')) la.addWidget(leglay_check) 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')) onedrawing_check.setChecked(True) la.addWidget(onedrawing_check) sewed_check = QtGui.QCheckBox(DialogBox) sewed_check.setObjectName('checkBox') la.addWidget(QtGui.QLabel('Sewed surfaces')) sewed_check.setChecked(True) sewed_check.setEnabled(False) la.addWidget(sewed_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()