When drawing a section view, first a list of objects to draw is made. Starting with the objects selected by the section plane, any groups or group-like objects are recursively expanded. The resulting list contains all contained objects, but also the expanded groups themselves (due to Draft.get_group_contents with addgroups set). This list of objects is then further categorized and processed. In this categorization, there was already an exception for DocumentObjectGroup instances, which were omitted from the resulting `nonspaces` list (presumably since there is no point in drawing the groups themselves when their contents are already in the list). However, any groups that only contain flat objects would be caught by `looksLikeDraf()` and added to the `drafts` list, before this exception could ignore them. This causes these groups to be processed by Draft.get_svg, which does so by calling itself recursively on the group contents. Effectively, this means that the contents of such groups is drawn on the section view twice. Even more, since Draft.get_svg does not do a visibility check like Arch.getSVG does, this causes invisible objects to show up when not intended. This commit fixes this by moving the DocumentObjectGroup exception a bit further up, so all these objects should be dropped. An alternative fix might be to pass addgroups=False to Draft.get_group_contents (in getSectionData), but that also prevents group-like objects (Building, BuildingPart, Space, Site) from being returned, but those likely need to be returned so they can be sectioned if needed (though if just spaces are needed, then Draft.get_group_contents also have a spaces argument to return just those).
1523 lines
61 KiB
Python
1523 lines
61 KiB
Python
#***************************************************************************
|
|
#* Copyright (c) 2011 Yorik van Havre <yorik@uncreated.net> *
|
|
#* *
|
|
#* 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 *
|
|
#* *
|
|
#***************************************************************************
|
|
|
|
import FreeCAD
|
|
import math
|
|
import Draft
|
|
import ArchCommands
|
|
import DraftVecUtils
|
|
import ArchComponent
|
|
import re
|
|
import tempfile
|
|
import uuid
|
|
import time
|
|
|
|
from FreeCAD import Vector
|
|
if FreeCAD.GuiUp:
|
|
import FreeCADGui
|
|
from PySide import QtCore, QtGui
|
|
from DraftTools import translate
|
|
from pivy import coin
|
|
from PySide.QtCore import QT_TRANSLATE_NOOP
|
|
else:
|
|
# \cond
|
|
def translate(ctxt,txt):
|
|
return txt
|
|
def QT_TRANSLATE_NOOP(ctxt,txt):
|
|
return txt
|
|
# \endcond
|
|
|
|
## @package ArchSectionPlane
|
|
# \ingroup ARCH
|
|
# \brief The Section plane object and tools
|
|
#
|
|
# This module provides tools to build Section plane objects.
|
|
# It also contains functionality to produce SVG rendering of
|
|
# section planes, to be used in TechDraw and Drawing modules
|
|
|
|
ISRENDERING = False # flag to prevent concurrent runs of the coin renderer
|
|
|
|
def makeSectionPlane(objectslist=None,name="Section"):
|
|
|
|
"""makeSectionPlane([objectslist]) : Creates a Section plane objects including the
|
|
given objects. If no object is given, the whole document will be considered."""
|
|
|
|
if not FreeCAD.ActiveDocument:
|
|
FreeCAD.Console.PrintError("No active document. Aborting\n")
|
|
return
|
|
obj = FreeCAD.ActiveDocument.addObject("App::FeaturePython",name)
|
|
obj.Label = translate("Arch",name)
|
|
_SectionPlane(obj)
|
|
if FreeCAD.GuiUp:
|
|
_ViewProviderSectionPlane(obj.ViewObject)
|
|
if objectslist:
|
|
obj.Objects = objectslist
|
|
bb = FreeCAD.BoundBox()
|
|
for o in Draft.get_group_contents(objectslist):
|
|
if hasattr(o,"Shape") and hasattr(o.Shape,"BoundBox"):
|
|
bb.add(o.Shape.BoundBox)
|
|
obj.Placement = FreeCAD.DraftWorkingPlane.getPlacement()
|
|
obj.Placement.Base = bb.Center
|
|
if FreeCAD.GuiUp:
|
|
margin = bb.XLength*0.1
|
|
obj.ViewObject.DisplayLength = bb.XLength+margin
|
|
obj.ViewObject.DisplayHeight = bb.YLength+margin
|
|
return obj
|
|
|
|
|
|
def makeSectionView(section,name="View"):
|
|
|
|
"""makeSectionView(section) : Creates a Drawing view of the given Section Plane
|
|
in the active Page object (a new page will be created if none exists"""
|
|
|
|
page = None
|
|
for o in FreeCAD.ActiveDocument.Objects:
|
|
if o.isDerivedFrom("Drawing::FeaturePage"):
|
|
page = o
|
|
break
|
|
if not page:
|
|
page = FreeCAD.ActiveDocument.addObject("Drawing::FeaturePage","Page")
|
|
page.Template = Draft.getParam("template",FreeCAD.getResourceDir()+'Mod/Drawing/Templates/A3_Landscape.svg')
|
|
|
|
view = FreeCAD.ActiveDocument.addObject("Drawing::FeatureViewPython",name)
|
|
page.addObject(view)
|
|
_ArchDrawingView(view)
|
|
view.Source = section
|
|
view.Label = translate("Arch","View of")+" "+section.Name
|
|
return view
|
|
|
|
|
|
def getSectionData(source):
|
|
|
|
"""Returns some common data from section planes and building parts"""
|
|
|
|
if hasattr(source,"Objects"):
|
|
objs = source.Objects
|
|
cutplane = source.Shape
|
|
elif hasattr(source,"Group"):
|
|
import Part
|
|
objs = source.Group
|
|
cutplane = Part.makePlane(1000,1000,FreeCAD.Vector(-500,-500,0))
|
|
m = 1
|
|
if source.ViewObject and hasattr(source.ViewObject,"CutMargin"):
|
|
m = source.ViewObject.CutMargin.Value
|
|
cutplane.translate(FreeCAD.Vector(0,0,m))
|
|
cutplane.Placement = cutplane.Placement.multiply(source.Placement)
|
|
onlySolids = True
|
|
if hasattr(source,"OnlySolids"):
|
|
onlySolids = source.OnlySolids
|
|
clip = False
|
|
if hasattr(source,"Clip"):
|
|
clip = source.Clip
|
|
p = FreeCAD.Placement(source.Placement)
|
|
direction = p.Rotation.multVec(FreeCAD.Vector(0,0,1))
|
|
if objs:
|
|
objs = Draft.get_group_contents(objs, walls=True, addgroups=True)
|
|
return objs,cutplane,onlySolids,clip,direction
|
|
|
|
|
|
def looksLikeDraft(o):
|
|
|
|
"""Does this object look like a Draft shape? (flat, no solid, etc)"""
|
|
|
|
# If there is no shape at all ignore it
|
|
if not hasattr(o, 'Shape') or o.Shape.isNull():
|
|
return False
|
|
# If there are solids in the object, it will be handled later
|
|
# by getCutShapes
|
|
if len(o.Shape.Solids) > 0:
|
|
return False
|
|
# If we have a shape, but no volume, it looks like a flat 2D object
|
|
return o.Shape.Volume < 0.0000001 # add a little tolerance...
|
|
|
|
|
|
def getCutShapes(objs,cutplane,onlySolids,clip,joinArch,showHidden,groupSshapesByObject=False):
|
|
|
|
"""
|
|
returns a list of shapes (visible, hidden, cut lines...)
|
|
obtained from performing a series of booleans against the given cut plane
|
|
"""
|
|
|
|
import Part,DraftGeomUtils
|
|
shapes = []
|
|
hshapes = []
|
|
sshapes = []
|
|
objectShapes = []
|
|
objectSshapes = []
|
|
|
|
if joinArch:
|
|
shtypes = {}
|
|
for o in objs:
|
|
if Draft.getType(o) in ["Wall","Structure"]:
|
|
if o.Shape.isNull():
|
|
pass
|
|
elif onlySolids:
|
|
shtypes.setdefault(o.Material.Name if (hasattr(o,"Material") and o.Material) else "None",[]).extend(o.Shape.Solids)
|
|
else:
|
|
shtypes.setdefault(o.Material.Name if (hasattr(o,"Material") and o.Material) else "None",[]).append(o.Shape.copy())
|
|
elif hasattr(o,'Shape'):
|
|
if o.Shape.isNull():
|
|
pass
|
|
elif onlySolids:
|
|
shapes.extend(o.Shape.Solids)
|
|
objectShapes.append((o, o.Shape.Solids))
|
|
else:
|
|
shapes.append(o.Shape.copy())
|
|
objectShapes.append((o,[o.Shape.copy()]))
|
|
for k,v in shtypes.items():
|
|
v1 = v.pop()
|
|
if v:
|
|
v1 = v1.multiFuse(v)
|
|
v1 = v1.removeSplitter()
|
|
if v1.Solids:
|
|
shapes.extend(v1.Solids)
|
|
objectShapes.append((k,v1.Solids))
|
|
else:
|
|
print("ArchSectionPlane: Fusing Arch objects produced non-solid results")
|
|
shapes.append(v1)
|
|
objectShapes.append((k,[v1]))
|
|
else:
|
|
for o in objs:
|
|
if hasattr(o,'Shape'):
|
|
if o.Shape.isNull():
|
|
pass
|
|
elif onlySolids:
|
|
if o.Shape.isValid():
|
|
shapes.extend(o.Shape.Solids)
|
|
objectShapes.append((o,o.Shape.Solids))
|
|
else:
|
|
shapes.append(o.Shape)
|
|
objectShapes.append((o,[o.Shape]))
|
|
|
|
cutface,cutvolume,invcutvolume = ArchCommands.getCutVolume(cutplane,shapes,clip)
|
|
shapes = []
|
|
for o, shapeList in objectShapes:
|
|
tmpSshapes = []
|
|
for sh in shapeList:
|
|
for sol in sh.Solids:
|
|
if cutvolume:
|
|
if sol.Volume < 0:
|
|
sol.reverse()
|
|
c = sol.cut(cutvolume)
|
|
s = sol.section(cutface)
|
|
try:
|
|
wires = DraftGeomUtils.findWires(s.Edges)
|
|
for w in wires:
|
|
f = Part.Face(w)
|
|
tmpSshapes.append(f)
|
|
#s = Part.Wire(s.Edges)
|
|
#s = Part.Face(s)
|
|
except Part.OCCError:
|
|
#print "ArchDrawingView: unable to get a face"
|
|
tmpSshapes.append(s)
|
|
shapes.extend(c.Solids)
|
|
#sshapes.append(s)
|
|
if showHidden:
|
|
c = sol.cut(invcutvolume)
|
|
hshapes.append(c)
|
|
else:
|
|
shapes.extend(sol.Solids)
|
|
|
|
if len(tmpSshapes) > 0:
|
|
sshapes.extend(tmpSshapes)
|
|
|
|
if groupSshapesByObject:
|
|
objectSshapes.append((o, tmpSshapes))
|
|
|
|
if groupSshapesByObject:
|
|
return shapes,hshapes,sshapes,cutface,cutvolume,invcutvolume,objectSshapes
|
|
else:
|
|
return shapes,hshapes,sshapes,cutface,cutvolume,invcutvolume
|
|
|
|
|
|
def getFillForObject(o, defaultFill, source):
|
|
|
|
"""returns a color tuple from an object's material"""
|
|
|
|
if hasattr(source, 'UseMaterialColorForFill') and source.UseMaterialColorForFill:
|
|
material = None
|
|
if hasattr(o, 'Material') and o.Material:
|
|
material = o.Material
|
|
elif isinstance(o,str):
|
|
material = FreeCAD.ActiveDocument.getObject(o)
|
|
if material:
|
|
if hasattr(material, 'SectionColor') and material.SectionColor:
|
|
return material.SectionColor
|
|
elif hasattr(material, 'Color') and material.Color:
|
|
return material.Color
|
|
return defaultFill
|
|
|
|
|
|
def isOriented(obj,plane):
|
|
|
|
"""determines if an annotation is facing the cutplane or not"""
|
|
|
|
norm1 = plane.normalAt(0,0)
|
|
if hasattr(obj,"Placement"):
|
|
norm2 = obj.Placement.Rotation.multVec(FreeCAD.Vector(0,0,1))
|
|
elif hasattr(obj,"Normal"):
|
|
norm2 = obj.Normal
|
|
if norm2.Length < 0.01:
|
|
return True
|
|
else:
|
|
return True
|
|
a = norm1.getAngle(norm2)
|
|
if (a < 0.01) or (abs(a-math.pi) < 0.01):
|
|
return True
|
|
return False
|
|
|
|
def update_svg_cache(source, renderMode, showHidden, showFill, fillSpaces, joinArch, allOn, objs):
|
|
"""
|
|
Returns None or cached SVG, clears shape cache if required
|
|
"""
|
|
svgcache = None
|
|
if hasattr(source,"Proxy"):
|
|
if hasattr(source.Proxy,"svgcache") and source.Proxy.svgcache:
|
|
# TODO check array bounds
|
|
svgcache = source.Proxy.svgcache[0]
|
|
# empty caches if we want to force-recalculate for certain properties
|
|
if (source.Proxy.svgcache[1] != renderMode
|
|
or source.Proxy.svgcache[2] != showHidden
|
|
or source.Proxy.svgcache[3] != showFill
|
|
or source.Proxy.svgcache[4] != fillSpaces
|
|
or source.Proxy.svgcache[5] != joinArch
|
|
or source.Proxy.svgcache[6] != allOn
|
|
or source.Proxy.svgcache[7] != set(objs)):
|
|
svgcache = None
|
|
if (source.Proxy.svgcache[4] != fillSpaces
|
|
or source.Proxy.svgcache[5] != joinArch
|
|
or source.Proxy.svgcache[6] != allOn
|
|
or source.Proxy.svgcache[7] != set(objs)):
|
|
source.Proxy.shapecache = None
|
|
return svgcache
|
|
|
|
|
|
def getSVG(source,
|
|
renderMode="Wireframe",
|
|
allOn=False,
|
|
showHidden=False,
|
|
scale=1,
|
|
rotation=0,
|
|
linewidth=1,
|
|
lineColor=(0.0, 0.0, 0.0),
|
|
fontsize=1,
|
|
showFill=False,
|
|
fillColor=(1.0, 1.0, 1.0),
|
|
techdraw=False,
|
|
fillSpaces=False,
|
|
cutlinewidth=0,
|
|
joinArch=False):
|
|
"""
|
|
Return an SVG fragment from an Arch SectionPlane or BuildingPart.
|
|
|
|
allOn
|
|
If it is `True`, all cut objects are shown, regardless of if they are
|
|
visible or not.
|
|
|
|
renderMode
|
|
Can be `'Wireframe'` (default) or `'Solid'` to use the Arch solid
|
|
renderer.
|
|
|
|
showHidden
|
|
If it is `True`, the hidden geometry above the section plane
|
|
is shown in dashed line.
|
|
|
|
showFill
|
|
If it is `True`, the cut areas get filled with a pattern.
|
|
|
|
lineColor
|
|
Color of lines for the `renderMode` is `'Wireframe'`.
|
|
|
|
fillColor
|
|
If `showFill` is `True` and `renderMode` is `'Wireframe'`,
|
|
the cut areas are filled with `fillColor`.
|
|
|
|
fillSpaces
|
|
If `True`, shows space objects as filled surfaces.
|
|
"""
|
|
import Part
|
|
|
|
objs, cutplane, onlySolids, clip, direction = getSectionData(source)
|
|
if not objs:
|
|
return ""
|
|
if not allOn:
|
|
objs = Draft.removeHidden(objs)
|
|
|
|
# separate spaces and Draft objects
|
|
spaces = []
|
|
nonspaces = []
|
|
drafts = []
|
|
windows = []
|
|
cutface = None
|
|
for o in objs:
|
|
if Draft.getType(o) == "Space":
|
|
spaces.append(o)
|
|
elif Draft.getType(o) in ["Dimension","AngularDimension","LinearDimension","Annotation","Label","Text", "DraftText"]:
|
|
if isOriented(o,cutplane):
|
|
drafts.append(o)
|
|
elif o.isDerivedFrom("Part::Part2DObject"):
|
|
drafts.append(o)
|
|
elif o.isDerivedFrom("App::DocumentObjectGroup"):
|
|
# These will have been expanded by getSectionData already
|
|
pass
|
|
elif looksLikeDraft(o):
|
|
drafts.append(o)
|
|
else:
|
|
nonspaces.append(o)
|
|
if Draft.getType(o.getLinkedObject()) == "Window": # To support Link of Windows(Doors)
|
|
windows.append(o)
|
|
objs = nonspaces
|
|
|
|
archUserParameters = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch")
|
|
scaledLineWidth = linewidth/scale
|
|
if renderMode in ["Coin",2,"Coin mono",3]:
|
|
# don't scale linewidths in coin mode
|
|
svgLineWidth = str(linewidth) + 'px'
|
|
else:
|
|
svgLineWidth = str(scaledLineWidth) + 'px'
|
|
if cutlinewidth:
|
|
scaledCutLineWidth = cutlinewidth/scale
|
|
svgCutLineWidth = str(scaledCutLineWidth) + 'px'
|
|
else:
|
|
st = archUserParameters.GetFloat("CutLineThickness",2)
|
|
svgCutLineWidth = str(scaledLineWidth * st) + 'px'
|
|
yt = archUserParameters.GetFloat("SymbolLineThickness",0.6)
|
|
svgSymbolLineWidth = str(linewidth * yt)
|
|
hiddenPattern = archUserParameters.GetString("archHiddenPattern","30,10")
|
|
svgHiddenPattern = hiddenPattern.replace(" ","")
|
|
#fillpattern = '<pattern id="sectionfill" patternUnits="userSpaceOnUse" patternTransform="matrix(5,0,0,5,0,0)"'
|
|
#fillpattern += ' x="0" y="0" width="10" height="10">'
|
|
#fillpattern += '<g>'
|
|
#fillpattern += '<rect width="10" height="10" style="stroke:none; fill:#ffffff" /><path style="stroke:#000000; stroke-width:1" d="M0,0 l10,10" /></g></pattern>'
|
|
svgLineColor = Draft.getrgb(lineColor)
|
|
svg = ''
|
|
# reading cached version
|
|
svgcache = update_svg_cache(source, renderMode, showHidden, showFill, fillSpaces, joinArch, allOn, objs)
|
|
should_update_svg_cache = False
|
|
if not svgcache:
|
|
should_update_svg_cache = True
|
|
|
|
# generating SVG
|
|
if renderMode in ["Coin",2,"Coin mono",3]:
|
|
# render using a coin viewer
|
|
if hasattr(source.ViewObject,"ViewData") and source.ViewObject.ViewData:
|
|
cameradata = None#getCameraData(source.ViewObject.ViewData)
|
|
else:
|
|
cameradata = None
|
|
if should_update_svg_cache:
|
|
if renderMode in ["Coin mono",3]:
|
|
svgcache = getCoinSVG(cutplane,objs,cameradata,linewidth="SVGLINEWIDTH",facecolor="#ffffff")
|
|
else:
|
|
svgcache = getCoinSVG(cutplane,objs,cameradata,linewidth="SVGLINEWIDTH")
|
|
elif renderMode in ["Solid",1]:
|
|
if should_update_svg_cache:
|
|
svgcache = ''
|
|
# render using the Arch Vector Renderer
|
|
import ArchVRM, WorkingPlane
|
|
wp = WorkingPlane.plane()
|
|
pl = FreeCAD.Placement(source.Placement)
|
|
if source.ViewObject and hasattr(source.ViewObject,"CutMargin"):
|
|
mv = pl.multVec(FreeCAD.Vector(0,0,1))
|
|
mv.multiply(source.ViewObject.CutMargin)
|
|
pl.move(mv)
|
|
wp.setFromPlacement(pl)
|
|
#wp.inverse()
|
|
render = ArchVRM.Renderer()
|
|
render.setWorkingPlane(wp)
|
|
render.addObjects(objs)
|
|
if showHidden:
|
|
render.cut(cutplane,showHidden)
|
|
else:
|
|
render.cut(cutplane)
|
|
g = '<g transform="scale(1,-1)">\n'
|
|
if hasattr(source.ViewObject,"RotateSolidRender"):
|
|
if (source.ViewObject.RotateSolidRender.Value != 0):
|
|
g = '<g transform="scale(1,-1) rotate('
|
|
g += str(source.ViewObject.RotateSolidRender.Value)
|
|
g += ')">\n'
|
|
svgcache += g
|
|
svgcache += render.getViewSVG(linewidth="SVGLINEWIDTH")
|
|
#svgcache += fillpattern
|
|
svgcache += render.getSectionSVG(linewidth="SVGCUTLINEWIDTH",fillpattern="#ffffff")
|
|
if showHidden:
|
|
svgcache += render.getHiddenSVG(linewidth="SVGLINEWIDTH")
|
|
svgcache += '</g>\n'
|
|
# print(render.info())
|
|
else:
|
|
# Wireframe (0) mode
|
|
|
|
if hasattr(source,"Proxy") and hasattr(source.Proxy,"shapecache") and source.Proxy.shapecache:
|
|
vshapes = source.Proxy.shapecache[0]
|
|
hshapes = source.Proxy.shapecache[1]
|
|
sshapes = source.Proxy.shapecache[2]
|
|
cutface = source.Proxy.shapecache[3]
|
|
cutvolume = source.Proxy.shapecache[4]
|
|
invcutvolume = source.Proxy.shapecache[5]
|
|
objectSshapes = source.Proxy.shapecache[6]
|
|
else:
|
|
if showFill:
|
|
vshapes,hshapes,sshapes,cutface,cutvolume,invcutvolume,objectSshapes = getCutShapes(objs,cutplane,onlySolids,clip,joinArch,showHidden,True)
|
|
else:
|
|
vshapes,hshapes,sshapes,cutface,cutvolume,invcutvolume = getCutShapes(objs,cutplane,onlySolids,clip,joinArch,showHidden)
|
|
objectSshapes = []
|
|
source.Proxy.shapecache = [vshapes,hshapes,sshapes,cutface,cutvolume,invcutvolume,objectSshapes]
|
|
|
|
if should_update_svg_cache:
|
|
svgcache = ""
|
|
# render using the Drawing module
|
|
import Drawing, Part
|
|
if vshapes:
|
|
baseshape = Part.makeCompound(vshapes)
|
|
style = {'stroke': "SVGLINECOLOR",
|
|
'stroke-width': "SVGLINEWIDTH"}
|
|
svgcache += Drawing.projectToSVG(
|
|
baseshape, direction,
|
|
hStyle=style, h0Style=style, h1Style=style,
|
|
vStyle=style, v0Style=style, v1Style=style)
|
|
if hshapes:
|
|
hshapes = Part.makeCompound(hshapes)
|
|
style = {'stroke': "SVGLINECOLOR",
|
|
'stroke-width': "SVGLINEWIDTH",
|
|
'stroke-dasharray': "SVGHIDDENPATTERN"}
|
|
svgcache += Drawing.projectToSVG(
|
|
hshapes, direction,
|
|
hStyle=style, h0Style=style, h1Style=style,
|
|
vStyle=style, v0Style=style, v1Style=style)
|
|
if sshapes:
|
|
if showFill:
|
|
#svgcache += fillpattern
|
|
svgcache += '<g transform="rotate(180)">\n'
|
|
for o, shapes in objectSshapes:
|
|
for s in shapes:
|
|
if s.Edges:
|
|
objectFill = getFillForObject(o, fillColor, source)
|
|
# svg += Draft.get_svg(s,
|
|
# direction=direction.negative(),
|
|
# linewidth=0,
|
|
# fillstyle="sectionfill",
|
|
# color=(0,0,0))
|
|
# temporarily disabling fill patterns
|
|
svgcache += Draft.get_svg(s,
|
|
linewidth=0,
|
|
fillstyle=Draft.getrgb(objectFill,testbw=False),
|
|
direction=direction.negative(),
|
|
color=lineColor)
|
|
svgcache += "</g>\n"
|
|
sshapes = Part.makeCompound(sshapes)
|
|
style = {'stroke': "SVGLINECOLOR",
|
|
'stroke-width': "SVGCUTLINEWIDTH"}
|
|
svgcache += Drawing.projectToSVG(
|
|
sshapes, direction,
|
|
hStyle=style, h0Style=style, h1Style=style,
|
|
vStyle=style, v0Style=style, v1Style=style)
|
|
if should_update_svg_cache:
|
|
if hasattr(source,"Proxy"):
|
|
source.Proxy.svgcache = [svgcache,renderMode,showHidden,showFill,fillSpaces,joinArch,allOn,set(objs)]
|
|
|
|
svgcache = svgcache.replace("SVGLINECOLOR",svgLineColor)
|
|
svgcache = svgcache.replace("SVGLINEWIDTH",svgLineWidth)
|
|
svgcache = svgcache.replace("SVGHIDDENPATTERN",svgHiddenPattern)
|
|
svgcache = svgcache.replace("SVGCUTLINEWIDTH",svgCutLineWidth)
|
|
svg += svgcache
|
|
|
|
if drafts:
|
|
if not techdraw:
|
|
svg += '<g transform="scale(1,-1)">'
|
|
for d in drafts:
|
|
svg += Draft.get_svg(d,
|
|
scale=scale,
|
|
linewidth=svgSymbolLineWidth,
|
|
fontsize=fontsize,
|
|
direction=direction,
|
|
color=lineColor,
|
|
techdraw=techdraw,
|
|
rotation=rotation)
|
|
if not techdraw:
|
|
svg += '</g>'
|
|
|
|
if not cutface:
|
|
# if we didn't calculate anything better, use the cutplane...
|
|
cutface = cutplane
|
|
|
|
# filter out spaces not cut by the source plane
|
|
if cutface and spaces:
|
|
spaces = [s for s in spaces if s.Shape.BoundBox.intersect(cutface.BoundBox)]
|
|
if spaces:
|
|
if not techdraw:
|
|
svg += '<g transform="scale(1,-1)">'
|
|
for s in spaces:
|
|
svg += Draft.get_svg(s,
|
|
scale=scale,
|
|
linewidth=svgSymbolLineWidth,
|
|
fontsize=fontsize,
|
|
direction=direction,
|
|
color=lineColor,
|
|
techdraw=techdraw,
|
|
rotation=rotation,
|
|
fillspaces=fillSpaces)
|
|
if not techdraw:
|
|
svg += '</g>'
|
|
|
|
# add additional edge symbols from windows
|
|
cutwindows = []
|
|
if cutface and windows:
|
|
cutwindows = [w.Name for w in windows if w.Shape.BoundBox.intersect(cutface.BoundBox)]
|
|
if windows:
|
|
sh = []
|
|
for w in windows:
|
|
wlo = w.getLinkedObject() # To support Link of Windows(Doors)
|
|
if not hasattr(wlo.Proxy,"sshapes"):
|
|
wlo.Proxy.execute(wlo)
|
|
if hasattr(wlo.Proxy,"sshapes"):
|
|
if wlo.Proxy.sshapes and (w.Name in cutwindows):
|
|
c = Part.makeCompound(wlo.Proxy.sshapes)
|
|
c.Placement = w.Placement
|
|
sh.append(c)
|
|
# buggy for now...
|
|
#if hasattr(w.Proxy,"vshapes"):
|
|
# if w.Proxy.vshapes:
|
|
# c = Part.makeCompound(w.Proxy.vshapes)
|
|
# c.Placement = w.Placement
|
|
# sh.append(c)
|
|
if sh:
|
|
if not techdraw:
|
|
svg += '<g transform="scale(1,-1)">'
|
|
for s in sh:
|
|
svg += Draft.get_svg(s,
|
|
scale=scale,
|
|
linewidth=svgSymbolLineWidth,
|
|
fontsize=fontsize,
|
|
fillstyle="none",
|
|
direction=direction,
|
|
color=lineColor,
|
|
techdraw=techdraw,
|
|
rotation=rotation)
|
|
if not techdraw:
|
|
svg += '</g>'
|
|
|
|
return svg
|
|
|
|
|
|
def getDXF(obj):
|
|
"""Return a DXF representation from a TechDraw/Drawing view."""
|
|
allOn = True
|
|
if hasattr(obj,"AllOn"):
|
|
allOn = obj.AllOn
|
|
elif hasattr(obj,"AlwaysOn"):
|
|
allOn = obj.AlwaysOn
|
|
showHidden = False
|
|
if hasattr(obj,"showCut"):
|
|
showHidden = obj.showCut
|
|
elif hasattr(obj,"showHidden"):
|
|
showHidden = obj.showHidden
|
|
result = []
|
|
import Drawing,Part
|
|
if not obj.Source:
|
|
return result
|
|
source = obj.Source
|
|
objs,cutplane,onlySolids,clip,direction = getSectionData(source)
|
|
if not objs:
|
|
return result
|
|
if not allOn:
|
|
objs = Draft.removeHidden(objs)
|
|
# separate spaces and Draft objects
|
|
spaces = []
|
|
nonspaces = []
|
|
drafts = []
|
|
objs = [o for o in objs if ((not(Draft.getType(o) in ["Space","Dimension","Annotation"])) and (not (o.isDerivedFrom("Part::Part2DObject"))))]
|
|
vshapes,hshapes,sshapes,cutface,cutvolume,invcutvolume = getCutShapes(objs,cutplane,onlySolids,clip,False,showHidden)
|
|
if vshapes:
|
|
result.append(Drawing.projectToDXF(Part.makeCompound(vshapes),direction))
|
|
if sshapes:
|
|
result.append(Drawing.projectToDXF(Part.makeCompound(sshapes),direction))
|
|
if hshapes:
|
|
result.append(Drawing.projectToDXF(Part.makeCompound(hshapes),direction))
|
|
return result
|
|
|
|
|
|
def getCameraData(floatlist):
|
|
|
|
"""reconstructs a valid camera data string from stored values"""
|
|
|
|
c = ""
|
|
if len(floatlist) >= 12:
|
|
d = floatlist
|
|
camtype = "orthographic"
|
|
if len(floatlist) == 13:
|
|
if d[12] == 1:
|
|
camtype = "perspective"
|
|
if camtype == "orthographic":
|
|
c = "#Inventor V2.1 ascii\n\n\nOrthographicCamera {\n viewportMapping ADJUST_CAMERA\n "
|
|
else:
|
|
c = "#Inventor V2.1 ascii\n\n\nPerspectiveCamera {\n viewportMapping ADJUST_CAMERA\n "
|
|
c += "position " + str(d[0]) + " " + str(d[1]) + " " + str(d[2]) + "\n "
|
|
c += "orientation " + str(d[3]) + " " + str(d[4]) + " " + str(d[5]) + " " + str(d[6]) + "\n "
|
|
c += "aspectRatio " + str(d[9]) + "\n "
|
|
c += "focalDistance " + str(d[10]) + "\n "
|
|
if camtype == "orthographic":
|
|
c += "height " + str(d[11]) + "\n\n}\n"
|
|
else:
|
|
c += "heightAngle " + str(d[11]) + "\n\n}\n"
|
|
return c
|
|
|
|
|
|
def getCoinSVG(cutplane,objs,cameradata=None,linewidth=0.2,singleface=False,facecolor=None):
|
|
|
|
"""Returns an SVG fragment generated from a coin view"""
|
|
|
|
if not FreeCAD.GuiUp:
|
|
return ""
|
|
|
|
# do not allow concurrent runs
|
|
# wait until the other rendering has finished
|
|
global ISRENDERING
|
|
while ISRENDERING:
|
|
time.sleep(0.1)
|
|
|
|
ISRENDERING = True
|
|
|
|
# a name to save a temp file
|
|
svgfile = tempfile.mkstemp(suffix=".svg")[1]
|
|
|
|
# set object lighting to single face to get black fills
|
|
# but this creates artifacts in svg output, triangulation gets visible...
|
|
ldict = {}
|
|
if singleface:
|
|
for obj in objs:
|
|
if hasattr(obj,"ViewObject") and hasattr(obj.ViewObject,"Lighting"):
|
|
ldict[obj.Name] = obj.ViewObject.Lighting
|
|
obj.ViewObject.Lighting = "One side"
|
|
|
|
# get nodes to render
|
|
root_node = coin.SoSeparator()
|
|
boundbox = FreeCAD.BoundBox()
|
|
for obj in objs:
|
|
if hasattr(obj.ViewObject,"RootNode") and obj.ViewObject.RootNode:
|
|
old_visibility = obj.ViewObject.isVisible()
|
|
# ignore visibility as only visible objects are passed here
|
|
obj.ViewObject.show()
|
|
node_copy = obj.ViewObject.RootNode.copy()
|
|
root_node.addChild(node_copy)
|
|
if(old_visibility):
|
|
obj.ViewObject.show()
|
|
else:
|
|
obj.ViewObject.hide()
|
|
|
|
if hasattr(obj,"Shape") and hasattr(obj.Shape,"BoundBox"):
|
|
boundbox.add(obj.Shape.BoundBox)
|
|
|
|
# reset lighting of objects
|
|
if ldict:
|
|
for obj in objs:
|
|
if obj.Name in ldict:
|
|
obj.ViewObject.Lighting = ldict[obj.Name]
|
|
|
|
# create viewer
|
|
view_window = FreeCADGui.createViewer()
|
|
view_window_name = "Temp" + str(uuid.uuid4().hex[:8])
|
|
view_window.setName(view_window_name)
|
|
inventor_view = view_window.getViewer()
|
|
|
|
inventor_view.setBackgroundColor(1,1,1)
|
|
view_window.redraw()
|
|
|
|
# set clip plane
|
|
clip = coin.SoClipPlane()
|
|
norm = cutplane.normalAt(0,0).negative()
|
|
proj = DraftVecUtils.project(cutplane.CenterOfMass,norm)
|
|
dist = proj.Length
|
|
if proj.getAngle(norm) > 1:
|
|
dist = -dist
|
|
clip.on = True
|
|
plane = coin.SbPlane(coin.SbVec3f(norm.x,norm.y,norm.z),dist) #dir, position on dir
|
|
clip.plane.setValue(plane)
|
|
root_node.insertChild(clip,0)
|
|
|
|
# add white marker at scene bound box corner
|
|
markervec = FreeCAD.Vector(10,10,10)
|
|
a = cutplane.normalAt(0,0).getAngle(markervec)
|
|
if (a < 0.01) or (abs(a-math.pi) < 0.01):
|
|
markervec = FreeCAD.Vector(10,-10,10)
|
|
boundbox.enlarge(10) # so the marker don't overlap the objects
|
|
sep = coin.SoSeparator()
|
|
mat = coin.SoMaterial()
|
|
mat.diffuseColor.setValue([1,1,1])
|
|
sep.addChild(mat)
|
|
coords = coin.SoCoordinate3()
|
|
coords.point.setValues([[boundbox.XMin,boundbox.YMin,boundbox.ZMin],
|
|
[boundbox.XMin+markervec.x,boundbox.YMin+markervec.y,boundbox.ZMin+markervec.z]])
|
|
sep.addChild(coords)
|
|
lset = coin.SoIndexedLineSet()
|
|
lset.coordIndex.setValues(0,2,[0,1])
|
|
sep.addChild(lset)
|
|
root_node.insertChild(sep,0)
|
|
|
|
# set scenegraph
|
|
inventor_view.setSceneGraph(root_node)
|
|
|
|
# set camera
|
|
if cameradata:
|
|
view_window.setCamera(cameradata)
|
|
else:
|
|
view_window.setCameraType("Orthographic")
|
|
#rot = FreeCAD.Rotation(FreeCAD.Vector(0,0,1),cutplane.normalAt(0,0))
|
|
vx = cutplane.Placement.Rotation.multVec(FreeCAD.Vector(1,0,0))
|
|
vy = cutplane.Placement.Rotation.multVec(FreeCAD.Vector(0,1,0))
|
|
vz = cutplane.Placement.Rotation.multVec(FreeCAD.Vector(0,0,1))
|
|
rot = FreeCAD.Rotation(vx,vy,vz,"ZXY")
|
|
view_window.setCameraOrientation(rot.Q)
|
|
# this is needed to set correct focal depth, otherwise saving doesn't work properly
|
|
view_window.fitAll()
|
|
|
|
# save view
|
|
#print("saving to",svgfile)
|
|
view_window.saveVectorGraphic(svgfile,1) # number is pixel size
|
|
|
|
# set linewidth placeholder
|
|
f = open(svgfile,"r")
|
|
svg = f.read()
|
|
f.close()
|
|
svg = svg.replace("stroke-width:1.0;","stroke-width:"+str(linewidth)+";")
|
|
svg = svg.replace("stroke-width=\"1px","stroke-width=\""+str(linewidth))
|
|
|
|
# find marker and calculate scale factor and translation
|
|
# <line x1="284.986" y1="356.166" x2="285.038" y2="356.166" stroke="#ffffff" stroke-width="1px" />
|
|
factor = None
|
|
trans = None
|
|
import WorkingPlane
|
|
wp = WorkingPlane.plane()
|
|
wp.alignToPointAndAxis_SVG(Vector(0,0,0),cutplane.normalAt(0,0),0)
|
|
p = wp.getLocalCoords(markervec)
|
|
orlength = FreeCAD.Vector(p.x,p.y,0).Length
|
|
marker = re.findall("<line x1=.*?stroke=\"\#ffffff\".*?\/>",svg)
|
|
if marker:
|
|
marker = marker[0].split("\"")
|
|
x1 = float(marker[1])
|
|
y1 = float(marker[3])
|
|
x2 = float(marker[5])
|
|
y2 = float(marker[7])
|
|
p1 = FreeCAD.Vector(x1,y1,0)
|
|
p2 = FreeCAD.Vector(x2,y2,0)
|
|
factor = orlength/p2.sub(p1).Length
|
|
if factor:
|
|
orig = wp.getLocalCoords(FreeCAD.Vector(boundbox.XMin,boundbox.YMin,boundbox.ZMin))
|
|
orig = FreeCAD.Vector(orig.x,-orig.y,0)
|
|
scaledp1 = FreeCAD.Vector(p1.x*factor,p1.y*factor,0)
|
|
trans = orig.sub(scaledp1)
|
|
# remove marker
|
|
svg = re.sub("<line x1=.*?stroke=\"\#ffffff\".*?\/>","",svg,count=1)
|
|
|
|
# remove background rectangle
|
|
svg = re.sub("<path.*?>","",svg,count=1,flags=re.MULTILINE|re.DOTALL)
|
|
|
|
# set face color to white
|
|
if facecolor:
|
|
res = re.findall("fill:(.*?); stroke:(.*?);",svg)
|
|
pairs = []
|
|
for pair in res:
|
|
if (pair not in pairs) and (pair[0] == pair[1]) and(pair[0] not in ["#0a0a0a"]):
|
|
# coin seems to be rendering a lot of lines as thin triangles with the #0a0a0a color...
|
|
pairs.append(pair)
|
|
for pair in pairs:
|
|
svg = re.sub("fill:"+pair[0]+"; stroke:"+pair[1]+";","fill:"+facecolor+"; stroke:"+facecolor+";",svg)
|
|
|
|
# embed everything in a scale group and scale the viewport
|
|
if factor:
|
|
if trans:
|
|
svg = svg.replace("<g>","<g transform=\"translate("+str(trans.x)+" "+str(trans.y)+") scale("+str(factor)+","+str(factor)+")\">\n<g>",1)
|
|
else:
|
|
svg = svg.replace("<g>","<g transform=\"scale("+str(factor)+","+str(factor)+")\">\n<g>",1)
|
|
svg = svg.replace("</svg>","</g>\n</svg>")
|
|
|
|
# trigger viewer close
|
|
QtCore.QTimer.singleShot(1,lambda: closeViewer(view_window_name))
|
|
|
|
# strip svg tags (needed for TD Arch view)
|
|
svg = re.sub("<\?xml.*?>","",svg,flags=re.MULTILINE|re.DOTALL)
|
|
svg = re.sub("<svg.*?>","",svg,flags=re.MULTILINE|re.DOTALL)
|
|
svg = re.sub("<\/svg>","",svg,flags=re.MULTILINE|re.DOTALL)
|
|
|
|
ISRENDERING = False
|
|
|
|
return svg
|
|
|
|
|
|
def closeViewer(name):
|
|
|
|
"""Close temporary viewers"""
|
|
|
|
mw = FreeCADGui.getMainWindow()
|
|
for sw in mw.findChildren(QtGui.QMdiSubWindow):
|
|
if sw.windowTitle() == name:
|
|
sw.close()
|
|
|
|
|
|
|
|
class _CommandSectionPlane:
|
|
|
|
"the Arch SectionPlane command definition"
|
|
|
|
def GetResources(self):
|
|
|
|
return {'Pixmap' : 'Arch_SectionPlane',
|
|
'Accel': "S, E",
|
|
'MenuText': QT_TRANSLATE_NOOP("Arch_SectionPlane","Section Plane"),
|
|
'ToolTip': QT_TRANSLATE_NOOP("Arch_SectionPlane","Creates a section plane object, including the selected objects")}
|
|
|
|
def IsActive(self):
|
|
|
|
return not FreeCAD.ActiveDocument is None
|
|
|
|
def Activated(self):
|
|
|
|
sel = FreeCADGui.Selection.getSelection()
|
|
ss = "["
|
|
for o in sel:
|
|
if len(ss) > 1:
|
|
ss += ","
|
|
ss += "FreeCAD.ActiveDocument."+o.Name
|
|
ss += "]"
|
|
FreeCAD.ActiveDocument.openTransaction(translate("Arch","Create Section Plane"))
|
|
FreeCADGui.addModule("Arch")
|
|
FreeCADGui.doCommand("section = Arch.makeSectionPlane("+ss+")")
|
|
#FreeCADGui.doCommand("section.Placement = FreeCAD.DraftWorkingPlane.getPlacement()")
|
|
#FreeCADGui.doCommand("Arch.makeSectionView(section)")
|
|
FreeCAD.ActiveDocument.commitTransaction()
|
|
FreeCAD.ActiveDocument.recompute()
|
|
|
|
|
|
class _SectionPlane:
|
|
|
|
"A section plane object"
|
|
|
|
def __init__(self,obj):
|
|
obj.Proxy = self
|
|
self.setProperties(obj)
|
|
|
|
def setProperties(self,obj):
|
|
|
|
pl = obj.PropertiesList
|
|
if not "Placement" in pl:
|
|
obj.addProperty("App::PropertyPlacement","Placement","SectionPlane",QT_TRANSLATE_NOOP("App::Property","The placement of this object"))
|
|
if not "Shape" in pl:
|
|
obj.addProperty("Part::PropertyPartShape","Shape","SectionPlane",QT_TRANSLATE_NOOP("App::Property","The shape of this object"))
|
|
if not "Objects" in pl:
|
|
obj.addProperty("App::PropertyLinkList","Objects","SectionPlane",QT_TRANSLATE_NOOP("App::Property","The objects that must be considered by this section plane. Empty means the whole document."))
|
|
if not "OnlySolids" in pl:
|
|
obj.addProperty("App::PropertyBool","OnlySolids","SectionPlane",QT_TRANSLATE_NOOP("App::Property","If false, non-solids will be cut too, with possible wrong results."))
|
|
obj.OnlySolids = True
|
|
if not "Clip" in pl:
|
|
obj.addProperty("App::PropertyBool","Clip","SectionPlane",QT_TRANSLATE_NOOP("App::Property","If True, resulting views will be clipped to the section plane area."))
|
|
if not "UseMaterialColorForFill" in pl:
|
|
obj.addProperty("App::PropertyBool","UseMaterialColorForFill","SectionPlane",QT_TRANSLATE_NOOP("App::Property","If true, the color of the objects material will be used to fill cut areas."))
|
|
obj.UseMaterialColorForFill = False
|
|
self.Type = "SectionPlane"
|
|
|
|
def onDocumentRestored(self,obj):
|
|
|
|
self.setProperties(obj)
|
|
|
|
def execute(self,obj):
|
|
|
|
import Part
|
|
l = 1
|
|
h = 1
|
|
if obj.ViewObject:
|
|
if hasattr(obj.ViewObject,"DisplayLength"):
|
|
l = obj.ViewObject.DisplayLength.Value
|
|
h = obj.ViewObject.DisplayHeight.Value
|
|
elif hasattr(obj.ViewObject,"DisplaySize"):
|
|
# old objects
|
|
l = obj.ViewObject.DisplaySize.Value
|
|
h = obj.ViewObject.DisplaySize.Value
|
|
p = Part.makePlane(l,h,Vector(l/2,-h/2,0),Vector(0,0,-1))
|
|
# make sure the normal direction is pointing outwards, you never know what OCC will decide...
|
|
if p.normalAt(0,0).getAngle(obj.Placement.Rotation.multVec(FreeCAD.Vector(0,0,1))) > 1:
|
|
p.reverse()
|
|
p.Placement = obj.Placement
|
|
obj.Shape = p
|
|
|
|
def onChanged(self,obj,prop):
|
|
|
|
# clean svg cache if needed
|
|
if prop in ["Placement","Objects","OnlySolids","UseMaterialColorForFill","Clip"]:
|
|
self.svgcache = None
|
|
self.shapecache = None
|
|
|
|
def getNormal(self,obj):
|
|
|
|
return obj.Shape.Faces[0].normalAt(0,0)
|
|
|
|
def __getstate__(self):
|
|
|
|
return None
|
|
|
|
def __setstate__(self,state):
|
|
|
|
return None
|
|
|
|
|
|
class _ViewProviderSectionPlane:
|
|
|
|
"A View Provider for Section Planes"
|
|
|
|
def __init__(self,vobj):
|
|
|
|
vobj.Proxy = self
|
|
self.setProperties(vobj)
|
|
|
|
def setProperties(self,vobj):
|
|
|
|
pl = vobj.PropertiesList
|
|
d = 0
|
|
if "DisplaySize" in pl:
|
|
d = vobj.DisplaySize.Value
|
|
vobj.removeProperty("DisplaySize")
|
|
if not "DisplayLength" in pl:
|
|
vobj.addProperty("App::PropertyLength","DisplayLength","SectionPlane",QT_TRANSLATE_NOOP("App::Property","The display length of this section plane"))
|
|
if d:
|
|
vobj.DisplayLength = d
|
|
else:
|
|
vobj.DisplayLength = 1000
|
|
if not "DisplayHeight" in pl:
|
|
vobj.addProperty("App::PropertyLength","DisplayHeight","SectionPlane",QT_TRANSLATE_NOOP("App::Property","The display height of this section plane"))
|
|
if d:
|
|
vobj.DisplayHeight = d
|
|
else:
|
|
vobj.DisplayHeight = 1000
|
|
if not "ArrowSize" in pl:
|
|
vobj.addProperty("App::PropertyLength","ArrowSize","SectionPlane",QT_TRANSLATE_NOOP("App::Property","The size of the arrows of this section plane"))
|
|
vobj.ArrowSize = 50
|
|
if not "Transparency" in pl:
|
|
vobj.addProperty("App::PropertyPercent","Transparency","SectionPlane",QT_TRANSLATE_NOOP("App::Property","The transparency of this object"))
|
|
vobj.Transparency = 85
|
|
if not "LineWidth" in pl:
|
|
vobj.addProperty("App::PropertyFloat","LineWidth","SectionPlane",QT_TRANSLATE_NOOP("App::Property","The line width of this object"))
|
|
vobj.LineWidth = 1
|
|
if not "CutDistance" in pl:
|
|
vobj.addProperty("App::PropertyLength","CutDistance","SectionPlane",QT_TRANSLATE_NOOP("App::Property","Show the cut in the 3D view"))
|
|
if not "LineColor" in pl:
|
|
vobj.addProperty("App::PropertyColor","LineColor","SectionPlane",QT_TRANSLATE_NOOP("App::Property","The color of this object"))
|
|
vobj.LineColor = ArchCommands.getDefaultColor("Helpers")
|
|
if not "CutView" in pl:
|
|
vobj.addProperty("App::PropertyBool","CutView","SectionPlane",QT_TRANSLATE_NOOP("App::Property","Show the cut in the 3D view"))
|
|
if not "CutMargin" in pl:
|
|
vobj.addProperty("App::PropertyLength","CutMargin","SectionPlane",QT_TRANSLATE_NOOP("App::Property","The distance between the cut plane and the actual view cut (keep this a very small value but not zero)"))
|
|
vobj.CutMargin = 1
|
|
if not "ShowLabel" in pl:
|
|
vobj.addProperty("App::PropertyBool","ShowLabel","SectionPlane",QT_TRANSLATE_NOOP("App::Property","Show the label in the 3D view"))
|
|
if not "FontName" in pl:
|
|
vobj.addProperty("App::PropertyFont","FontName", "SectionPlane",QT_TRANSLATE_NOOP("App::Property","The name of the font"))
|
|
vobj.FontName = Draft.getParam("textfont","")
|
|
if not "FontSize" in pl:
|
|
vobj.addProperty("App::PropertyLength","FontSize","SectionPlane",QT_TRANSLATE_NOOP("App::Property","The size of the text font"))
|
|
vobj.FontSize = Draft.getParam("textheight",10)
|
|
|
|
|
|
def onDocumentRestored(self,vobj):
|
|
|
|
self.setProperties(vobj)
|
|
|
|
def getIcon(self):
|
|
|
|
import Arch_rc
|
|
return ":/icons/Arch_SectionPlane_Tree.svg"
|
|
|
|
def claimChildren(self):
|
|
# buggy at the moment so it's disabled - it will for ex. swallow a building object directly at the root of the document...
|
|
#if hasattr(self,"Object") and hasattr(self.Object,"Objects"):
|
|
# return self.Object.Objects
|
|
return []
|
|
|
|
def attach(self,vobj):
|
|
|
|
self.Object = vobj.Object
|
|
self.clip = None
|
|
self.mat1 = coin.SoMaterial()
|
|
self.mat2 = coin.SoMaterial()
|
|
self.fcoords = coin.SoCoordinate3()
|
|
#fs = coin.SoType.fromName("SoBrepFaceSet").createInstance() # this causes a FreeCAD freeze for me
|
|
fs = coin.SoIndexedFaceSet()
|
|
fs.coordIndex.setValues(0,7,[0,1,2,-1,0,2,3])
|
|
self.drawstyle = coin.SoDrawStyle()
|
|
self.drawstyle.style = coin.SoDrawStyle.LINES
|
|
self.lcoords = coin.SoCoordinate3()
|
|
ls = coin.SoType.fromName("SoBrepEdgeSet").createInstance()
|
|
ls.coordIndex.setValues(0,57,[0,1,-1,2,3,4,5,-1,6,7,8,9,-1,10,11,-1,12,13,14,15,-1,16,17,18,19,-1,20,21,-1,22,23,24,25,-1,26,27,28,29,-1,30,31,-1,32,33,34,35,-1,36,37,38,39,-1,40,41,42,43,44])
|
|
self.txtcoords = coin.SoTransform()
|
|
self.txtfont = coin.SoFont()
|
|
self.txtfont.name = ""
|
|
self.txt = coin.SoAsciiText()
|
|
self.txt.justification = coin.SoText2.LEFT
|
|
self.txt.string.setValue(" ")
|
|
sep = coin.SoSeparator()
|
|
psep = coin.SoSeparator()
|
|
fsep = coin.SoSeparator()
|
|
tsep = coin.SoSeparator()
|
|
fsep.addChild(self.mat2)
|
|
fsep.addChild(self.fcoords)
|
|
fsep.addChild(fs)
|
|
psep.addChild(self.mat1)
|
|
psep.addChild(self.drawstyle)
|
|
psep.addChild(self.lcoords)
|
|
psep.addChild(ls)
|
|
tsep.addChild(self.mat1)
|
|
tsep.addChild(self.txtcoords)
|
|
tsep.addChild(self.txtfont)
|
|
tsep.addChild(self.txt)
|
|
sep.addChild(fsep)
|
|
sep.addChild(psep)
|
|
sep.addChild(tsep)
|
|
vobj.addDisplayMode(sep,"Default")
|
|
self.onChanged(vobj,"DisplayLength")
|
|
self.onChanged(vobj,"LineColor")
|
|
self.onChanged(vobj,"Transparency")
|
|
self.onChanged(vobj,"CutView")
|
|
|
|
def getDisplayModes(self,vobj):
|
|
|
|
return ["Default"]
|
|
|
|
def getDefaultDisplayMode(self):
|
|
|
|
return "Default"
|
|
|
|
def setDisplayMode(self,mode):
|
|
|
|
return mode
|
|
|
|
def updateData(self,obj,prop):
|
|
|
|
if prop in ["Placement"]:
|
|
# for some reason the text doesn't rotate with the host placement??
|
|
self.txtcoords.rotation.setValue(obj.Placement.Rotation.Q)
|
|
self.onChanged(obj.ViewObject,"DisplayLength")
|
|
self.onChanged(obj.ViewObject,"CutView")
|
|
elif prop == "Label":
|
|
if hasattr(obj.ViewObject,"ShowLabel") and obj.ViewObject.ShowLabel:
|
|
self.txt.string = obj.Label
|
|
return
|
|
|
|
def onChanged(self,vobj,prop):
|
|
|
|
if prop == "LineColor":
|
|
if hasattr(vobj,"LineColor"):
|
|
l = vobj.LineColor
|
|
self.mat1.diffuseColor.setValue([l[0],l[1],l[2]])
|
|
self.mat2.diffuseColor.setValue([l[0],l[1],l[2]])
|
|
elif prop == "Transparency":
|
|
if hasattr(vobj,"Transparency"):
|
|
self.mat2.transparency.setValue(vobj.Transparency/100.0)
|
|
elif prop in ["DisplayLength","DisplayHeight","ArrowSize"]:
|
|
if hasattr(vobj,"DisplayLength") and hasattr(vobj,"DisplayHeight"):
|
|
ld = vobj.DisplayLength.Value/2
|
|
hd = vobj.DisplayHeight.Value/2
|
|
elif hasattr(vobj,"DisplaySize"):
|
|
# old objects
|
|
ld = vobj.DisplaySize.Value/2
|
|
hd = vobj.DisplaySize.Value/2
|
|
else:
|
|
ld = 1
|
|
hd = 1
|
|
verts = []
|
|
fverts = []
|
|
pl = FreeCAD.Placement(vobj.Object.Placement)
|
|
if hasattr(vobj,"ArrowSize"):
|
|
l1 = vobj.ArrowSize.Value if vobj.ArrowSize.Value > 0 else 0.1
|
|
else:
|
|
l1 = 0.1
|
|
l2 = l1/3
|
|
for v in [[-ld,-hd],[ld,-hd],[ld,hd],[-ld,hd]]:
|
|
p1 = pl.multVec(Vector(v[0],v[1],0))
|
|
p2 = pl.multVec(Vector(v[0],v[1],-l1))
|
|
p3 = pl.multVec(Vector(v[0]-l2,v[1],-l1+l2))
|
|
p4 = pl.multVec(Vector(v[0]+l2,v[1],-l1+l2))
|
|
p5 = pl.multVec(Vector(v[0],v[1]-l2,-l1+l2))
|
|
p6 = pl.multVec(Vector(v[0],v[1]+l2,-l1+l2))
|
|
verts.extend([[p1.x,p1.y,p1.z],[p2.x,p2.y,p2.z]])
|
|
fverts.append([p1.x,p1.y,p1.z])
|
|
verts.extend([[p2.x,p2.y,p2.z],[p3.x,p3.y,p3.z],[p4.x,p4.y,p4.z],[p2.x,p2.y,p2.z]])
|
|
verts.extend([[p2.x,p2.y,p2.z],[p5.x,p5.y,p5.z],[p6.x,p6.y,p6.z],[p2.x,p2.y,p2.z]])
|
|
p7 = pl.multVec(Vector(-ld+l2,-hd+l2,0)) # text pos
|
|
verts.extend(fverts+[fverts[0]])
|
|
self.lcoords.point.setValues(verts)
|
|
self.fcoords.point.setValues(fverts)
|
|
self.txtcoords.translation.setValue([p7.x,p7.y,p7.z])
|
|
#self.txtfont.size = l1
|
|
elif prop == "LineWidth":
|
|
self.drawstyle.lineWidth = vobj.LineWidth
|
|
elif prop in ["CutView","CutMargin"]:
|
|
if hasattr(vobj,"CutView") and FreeCADGui.ActiveDocument.ActiveView:
|
|
sg = FreeCADGui.ActiveDocument.ActiveView.getSceneGraph()
|
|
if vobj.CutView:
|
|
if self.clip:
|
|
sg.removeChild(self.clip)
|
|
self.clip = None
|
|
for o in Draft.get_group_contents(vobj.Object.Objects,
|
|
walls=True):
|
|
if hasattr(o.ViewObject,"Lighting"):
|
|
o.ViewObject.Lighting = "One side"
|
|
self.clip = coin.SoClipPlane()
|
|
self.clip.on.setValue(True)
|
|
norm = vobj.Object.Proxy.getNormal(vobj.Object)
|
|
mp = vobj.Object.Shape.CenterOfMass
|
|
mp = DraftVecUtils.project(mp,norm)
|
|
dist = mp.Length #- 0.1 # to not clip exactly on the section object
|
|
norm = norm.negative()
|
|
marg = 1
|
|
if hasattr(vobj,"CutMargin"):
|
|
marg = vobj.CutMargin.Value
|
|
if mp.getAngle(norm) > 1:
|
|
dist += marg
|
|
dist = -dist
|
|
else:
|
|
dist -= marg
|
|
plane = coin.SbPlane(coin.SbVec3f(norm.x,norm.y,norm.z),dist)
|
|
self.clip.plane.setValue(plane)
|
|
sg.insertChild(self.clip,0)
|
|
else:
|
|
if self.clip:
|
|
sg.removeChild(self.clip)
|
|
self.clip = None
|
|
elif prop == "ShowLabel":
|
|
if vobj.ShowLabel:
|
|
self.txt.string = vobj.Object.Label or " "
|
|
else:
|
|
self.txt.string = " "
|
|
elif prop == "FontName":
|
|
if hasattr(self,"txtfont") and hasattr(vobj,"FontName"):
|
|
if vobj.FontName:
|
|
self.txtfont.name = vobj.FontName
|
|
else:
|
|
self.txtfont.name = ""
|
|
elif prop == "FontSize":
|
|
if hasattr(self,"txtfont") and hasattr(vobj,"FontSize"):
|
|
self.txtfont.size = vobj.FontSize.Value
|
|
return
|
|
|
|
def __getstate__(self):
|
|
|
|
return None
|
|
|
|
def __setstate__(self,state):
|
|
|
|
return None
|
|
|
|
def setEdit(self,vobj,mode):
|
|
|
|
taskd = SectionPlaneTaskPanel()
|
|
taskd.obj = vobj.Object
|
|
taskd.update()
|
|
FreeCADGui.Control.showDialog(taskd)
|
|
return True
|
|
|
|
def unsetEdit(self,vobj,mode):
|
|
|
|
FreeCADGui.Control.closeDialog()
|
|
return False
|
|
|
|
def doubleClicked(self,vobj):
|
|
|
|
self.setEdit(vobj,None)
|
|
|
|
def setupContextMenu(self,vobj,menu):
|
|
"""CONTEXT MENU setup"""
|
|
from PySide import QtCore,QtGui
|
|
action1 = QtGui.QAction(QtGui.QIcon(":/icons/Draft_Edit.svg"),"Toggle Cutview",menu)
|
|
action1.triggered.connect(lambda f=self.contextCutview, arg=vobj:f(arg))
|
|
menu.addAction(action1)
|
|
|
|
def contextCutview(self,vobj):
|
|
"""CONTEXT MENU command to toggle CutView property on and off"""
|
|
if vobj.CutView:
|
|
vobj.CutView = False
|
|
else: vobj.CutView = True
|
|
|
|
|
|
class _ArchDrawingView:
|
|
|
|
def __init__(self, obj):
|
|
|
|
obj.Proxy = self
|
|
self.setProperties(obj)
|
|
|
|
def setProperties(self,obj):
|
|
|
|
pl = obj.PropertiesList
|
|
if not "Source" in pl:
|
|
obj.addProperty("App::PropertyLink", "Source", "Base", QT_TRANSLATE_NOOP("App::Property","The linked object"))
|
|
if not "RenderingMode" in pl:
|
|
obj.addProperty("App::PropertyEnumeration", "RenderingMode", "Drawing view", QT_TRANSLATE_NOOP("App::Property","The rendering mode to use"))
|
|
obj.RenderingMode = ["Solid","Wireframe"]
|
|
obj.RenderingMode = "Wireframe"
|
|
if not "ShowCut" in pl:
|
|
obj.addProperty("App::PropertyBool", "ShowCut", "Drawing view", QT_TRANSLATE_NOOP("App::Property","If cut geometry is shown or not"))
|
|
if not "ShowFill" in pl:
|
|
obj.addProperty("App::PropertyBool", "ShowFill", "Drawing view", QT_TRANSLATE_NOOP("App::Property","If cut geometry is filled or not"))
|
|
if not "LineWidth" in pl:
|
|
obj.addProperty("App::PropertyFloat", "LineWidth", "Drawing view", QT_TRANSLATE_NOOP("App::Property","The line width of the rendered objects"))
|
|
obj.LineWidth = 0.35
|
|
if not "FontSize" in pl:
|
|
obj.addProperty("App::PropertyLength", "FontSize", "Drawing view", QT_TRANSLATE_NOOP("App::Property","The size of the texts inside this object"))
|
|
obj.FontSize = 12
|
|
if not "AlwaysOn" in pl:
|
|
obj.addProperty("App::PropertyBool", "AlwaysOn", "Drawing view", QT_TRANSLATE_NOOP("App::Property","If checked, source objects are displayed regardless of being visible in the 3D model"))
|
|
if not "LineColor" in pl:
|
|
obj.addProperty("App::PropertyColor", "LineColor", "Drawing view",QT_TRANSLATE_NOOP("App::Property","The line color of the projected objects"))
|
|
if not "FillColor" in pl:
|
|
obj.addProperty("App::PropertyColor", "FillColor", "Drawing view",QT_TRANSLATE_NOOP("App::Property","The color of the cut faces (if turned on)"))
|
|
obj.FillColor = (0.8, 0.8, 0.8)
|
|
self.Type = "ArchSectionView"
|
|
|
|
def onDocumentRestored(self, obj):
|
|
|
|
self.setProperties(obj)
|
|
|
|
def execute(self, obj):
|
|
|
|
if hasattr(obj,"Source"):
|
|
if obj.Source:
|
|
svgbody = getSVG(source=obj.Source,
|
|
renderMode=obj.RenderingMode,
|
|
allOn=getattr(obj, 'AlwaysOn', False),
|
|
showHidden=obj.ShowCut,
|
|
scale=obj.Scale,
|
|
linewidth=obj.LineWidth,
|
|
lineColor=obj.LineColor,
|
|
fontsize=obj.FontSize,
|
|
showFill=obj.ShowFill,
|
|
fillColor=obj.FillColor)
|
|
if svgbody:
|
|
result = '<g id="' + obj.Name + '"'
|
|
result += ' transform="'
|
|
result += 'rotate('+str(obj.Rotation)+','+str(obj.X)+','+str(obj.Y)+') '
|
|
result += 'translate('+str(obj.X)+','+str(obj.Y)+') '
|
|
result += 'scale('+str(obj.Scale)+','+str(obj.Scale)+')'
|
|
result += '">\n'
|
|
result += svgbody
|
|
result += '</g>\n'
|
|
obj.ViewResult = result
|
|
|
|
def __getstate__(self):
|
|
|
|
return self.Type
|
|
|
|
def __setstate__(self,state):
|
|
|
|
if state:
|
|
self.Type = state
|
|
|
|
def getDisplayModes(self,vobj):
|
|
|
|
modes=["Default"]
|
|
return modes
|
|
|
|
def setDisplayMode(self,mode):
|
|
|
|
return mode
|
|
|
|
|
|
class SectionPlaneTaskPanel:
|
|
|
|
'''A TaskPanel for all the section plane object'''
|
|
|
|
def __init__(self):
|
|
|
|
# the panel has a tree widget that contains categories
|
|
# for the subcomponents, such as additions, subtractions.
|
|
# the categories are shown only if they are not empty.
|
|
|
|
self.obj = None
|
|
self.form = QtGui.QWidget()
|
|
self.form.setObjectName("TaskPanel")
|
|
self.grid = QtGui.QGridLayout(self.form)
|
|
self.title = QtGui.QLabel(self.form)
|
|
self.grid.addWidget(self.title, 0, 0, 1, 2)
|
|
|
|
# tree
|
|
self.tree = QtGui.QTreeWidget(self.form)
|
|
self.grid.addWidget(self.tree, 1, 0, 1, 2)
|
|
self.tree.setColumnCount(1)
|
|
self.tree.header().hide()
|
|
|
|
# add / remove buttons
|
|
self.addButton = QtGui.QPushButton(self.form)
|
|
self.addButton.setIcon(QtGui.QIcon(":/icons/Arch_Add.svg"))
|
|
self.grid.addWidget(self.addButton, 3, 0, 1, 1)
|
|
|
|
self.delButton = QtGui.QPushButton(self.form)
|
|
self.delButton.setIcon(QtGui.QIcon(":/icons/Arch_Remove.svg"))
|
|
self.grid.addWidget(self.delButton, 3, 1, 1, 1)
|
|
self.delButton.setEnabled(False)
|
|
|
|
# rotate / resize buttons
|
|
self.rlabel = QtGui.QLabel(self.form)
|
|
self.grid.addWidget(self.rlabel, 4, 0, 1, 2)
|
|
self.rotateXButton = QtGui.QPushButton(self.form)
|
|
self.grid.addWidget(self.rotateXButton, 5, 0, 1, 1)
|
|
self.rotateYButton = QtGui.QPushButton(self.form)
|
|
self.grid.addWidget(self.rotateYButton, 5, 1, 1, 1)
|
|
self.rotateZButton = QtGui.QPushButton(self.form)
|
|
self.grid.addWidget(self.rotateZButton, 6, 0, 1, 1)
|
|
self.resizeButton = QtGui.QPushButton(self.form)
|
|
self.grid.addWidget(self.resizeButton, 7, 0, 1, 1)
|
|
self.recenterButton = QtGui.QPushButton(self.form)
|
|
self.grid.addWidget(self.recenterButton, 7, 1, 1, 1)
|
|
|
|
QtCore.QObject.connect(self.addButton, QtCore.SIGNAL("clicked()"), self.addElement)
|
|
QtCore.QObject.connect(self.delButton, QtCore.SIGNAL("clicked()"), self.removeElement)
|
|
QtCore.QObject.connect(self.rotateXButton, QtCore.SIGNAL("clicked()"), self.rotateX)
|
|
QtCore.QObject.connect(self.rotateYButton, QtCore.SIGNAL("clicked()"), self.rotateY)
|
|
QtCore.QObject.connect(self.rotateZButton, QtCore.SIGNAL("clicked()"), self.rotateZ)
|
|
QtCore.QObject.connect(self.resizeButton, QtCore.SIGNAL("clicked()"), self.resize)
|
|
QtCore.QObject.connect(self.recenterButton, QtCore.SIGNAL("clicked()"), self.recenter)
|
|
QtCore.QObject.connect(self.tree, QtCore.SIGNAL("itemSelectionChanged()"), self.onTreeClick)
|
|
self.update()
|
|
|
|
def isAllowedAlterSelection(self):
|
|
return True
|
|
|
|
def isAllowedAlterView(self):
|
|
return True
|
|
|
|
def getStandardButtons(self):
|
|
return int(QtGui.QDialogButtonBox.Ok)
|
|
|
|
def getIcon(self,obj):
|
|
if hasattr(obj.ViewObject,"Proxy"):
|
|
return QtGui.QIcon(obj.ViewObject.Proxy.getIcon())
|
|
elif obj.isDerivedFrom("Sketcher::SketchObject"):
|
|
return QtGui.QIcon(":/icons/Sketcher_Sketch.svg")
|
|
elif obj.isDerivedFrom("App::DocumentObjectGroup"):
|
|
return QtGui.QApplication.style().standardIcon(QtGui.QStyle.SP_DirIcon)
|
|
elif hasattr(obj.ViewObject, "Icon"):
|
|
return QtGui.QIcon(obj.ViewObject.Icon)
|
|
return QtGui.QIcon(":/icons/Part_3D_object.svg")
|
|
|
|
def update(self):
|
|
'fills the treewidget'
|
|
self.tree.clear()
|
|
if self.obj:
|
|
for o in self.obj.Objects:
|
|
item = QtGui.QTreeWidgetItem(self.tree)
|
|
item.setText(0,o.Label)
|
|
item.setToolTip(0,o.Name)
|
|
item.setIcon(0,self.getIcon(o))
|
|
self.retranslateUi(self.form)
|
|
|
|
def addElement(self):
|
|
if self.obj:
|
|
added = False
|
|
for o in FreeCADGui.Selection.getSelection():
|
|
if o != self.obj:
|
|
ArchComponent.addToComponent(self.obj,o,"Objects")
|
|
added = True
|
|
if added:
|
|
self.update()
|
|
else:
|
|
FreeCAD.Console.PrintWarning("Please select objects in the 3D view or in the model tree before pressing the button\n")
|
|
|
|
def removeElement(self):
|
|
if self.obj:
|
|
it = self.tree.currentItem()
|
|
if it:
|
|
comp = FreeCAD.ActiveDocument.getObject(str(it.toolTip(0)))
|
|
ArchComponent.removeFromComponent(self.obj,comp)
|
|
self.update()
|
|
|
|
def rotate(self,axis):
|
|
if self.obj and self.obj.Shape and self.obj.Shape.Faces:
|
|
face = self.obj.Shape.copy()
|
|
import Part
|
|
face.rotate(self.obj.Placement.Base, axis, 90)
|
|
self.obj.Placement = face.Placement
|
|
self.obj.Proxy.execute(self.obj)
|
|
|
|
def rotateX(self):
|
|
self.rotate(FreeCAD.Vector(1,0,0))
|
|
|
|
def rotateY(self):
|
|
self.rotate(FreeCAD.Vector(0,1,0))
|
|
|
|
def rotateZ(self):
|
|
self.rotate(FreeCAD.Vector(0,0,1))
|
|
|
|
def getBB(self):
|
|
bb = FreeCAD.BoundBox()
|
|
if self.obj:
|
|
for o in Draft.get_group_contents(self.obj.Objects):
|
|
if hasattr(o,"Shape") and hasattr(o.Shape,"BoundBox"):
|
|
bb.add(o.Shape.BoundBox)
|
|
return bb
|
|
|
|
def resize(self):
|
|
if self.obj and self.obj.ViewObject:
|
|
bb = self.getBB()
|
|
n = self.obj.Proxy.getNormal(self.obj)
|
|
margin = bb.XLength*0.1
|
|
if (n.getAngle(FreeCAD.Vector(1,0,0)) < 0.1) or (n.getAngle(FreeCAD.Vector(-1,0,0)) < 0.1):
|
|
self.obj.ViewObject.DisplayLength = bb.YLength+margin
|
|
self.obj.ViewObject.DisplayHeight = bb.ZLength+margin
|
|
elif (n.getAngle(FreeCAD.Vector(0,1,0)) < 0.1) or (n.getAngle(FreeCAD.Vector(0,-1,0)) < 0.1):
|
|
self.obj.ViewObject.DisplayLength = bb.XLength+margin
|
|
self.obj.ViewObject.DisplayHeight = bb.ZLength+margin
|
|
elif (n.getAngle(FreeCAD.Vector(0,0,1)) < 0.1) or (n.getAngle(FreeCAD.Vector(0,0,-1)) < 0.1):
|
|
self.obj.ViewObject.DisplayLength = bb.XLength+margin
|
|
self.obj.ViewObject.DisplayHeight = bb.YLength+margin
|
|
self.obj.Proxy.execute(self.obj)
|
|
|
|
def recenter(self):
|
|
if self.obj:
|
|
self.obj.Placement.Base = self.getBB().Center
|
|
|
|
def onTreeClick(self):
|
|
if self.tree.selectedItems():
|
|
self.delButton.setEnabled(True)
|
|
else:
|
|
self.delButton.setEnabled(False)
|
|
|
|
def accept(self):
|
|
FreeCAD.ActiveDocument.recompute()
|
|
FreeCADGui.ActiveDocument.resetEdit()
|
|
return True
|
|
|
|
def retranslateUi(self, TaskPanel):
|
|
TaskPanel.setWindowTitle(QtGui.QApplication.translate("Arch", "Section plane settings", None))
|
|
self.delButton.setText(QtGui.QApplication.translate("Arch", "Remove", None))
|
|
self.delButton.setToolTip(QtGui.QApplication.translate("Arch", "Remove highlighted objects from the list above", None))
|
|
self.addButton.setText(QtGui.QApplication.translate("Arch", "Add selected", None))
|
|
self.addButton.setToolTip(QtGui.QApplication.translate("Arch", "Add selected object(s) to the scope of this section plane", None))
|
|
self.title.setText(QtGui.QApplication.translate("Arch", "Objects seen by this section plane:", None))
|
|
self.rlabel.setText(QtGui.QApplication.translate("Arch", "Section plane placement:", None))
|
|
self.rotateXButton.setText(QtGui.QApplication.translate("Arch", "Rotate X", None))
|
|
self.rotateXButton.setToolTip(QtGui.QApplication.translate("Arch", "Rotates the plane along the X axis", None))
|
|
self.rotateYButton.setText(QtGui.QApplication.translate("Arch", "Rotate Y", None))
|
|
self.rotateYButton.setToolTip(QtGui.QApplication.translate("Arch", "Rotates the plane along the Y axis", None))
|
|
self.rotateZButton.setText(QtGui.QApplication.translate("Arch", "Rotate Z", None))
|
|
self.rotateZButton.setToolTip(QtGui.QApplication.translate("Arch", "Rotates the plane along the Z axis", None))
|
|
self.resizeButton.setText(QtGui.QApplication.translate("Arch", "Resize", None))
|
|
self.resizeButton.setToolTip(QtGui.QApplication.translate("Arch", "Resizes the plane to fit the objects in the list above", None))
|
|
self.recenterButton.setText(QtGui.QApplication.translate("Arch", "Center", None))
|
|
self.recenterButton.setToolTip(QtGui.QApplication.translate("Arch", "Centers the plane on the objects in the list above", None))
|
|
|
|
if FreeCAD.GuiUp:
|
|
FreeCADGui.addCommand('Arch_SectionPlane',_CommandSectionPlane())
|