FreeCAD / src /Mod /BIM /ArchSectionPlane.py
AbdulElahGwaith's picture
Upload folder using huggingface_hub
985c397 verified
# SPDX-License-Identifier: LGPL-2.1-or-later
# ***************************************************************************
# * *
# * Copyright (c) 2011 Yorik van Havre <yorik@uncreated.net> *
# * *
# * This file is part of FreeCAD. *
# * *
# * FreeCAD is free software: you can redistribute it and/or modify it *
# * under the terms of the GNU Lesser General Public License as *
# * published by the Free Software Foundation, either version 2.1 of the *
# * License, or (at your option) any later version. *
# * *
# * FreeCAD 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 *
# * Lesser General Public License for more details. *
# * *
# * You should have received a copy of the GNU Lesser General Public *
# * License along with FreeCAD. If not, see *
# * <https://www.gnu.org/licenses/>. *
# * *
# ***************************************************************************
## @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 the TechDraw module
import math
import re
import tempfile
import time
import uuid
import FreeCAD
import ArchCommands
import ArchComponent
import Draft
import DraftVecUtils
from FreeCAD import Vector
from draftutils import params
if FreeCAD.GuiUp:
from pivy import coin
from PySide import QtCore, QtGui
from PySide.QtCore import QT_TRANSLATE_NOOP
import FreeCADGui
from draftutils.translate import translate
else:
# \cond
def translate(ctxt, txt):
return txt
def QT_TRANSLATE_NOOP(ctxt, txt):
return txt
# \endcond
ISRENDERING = False # flag to prevent concurrent runs of the coin renderer
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 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
import 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 sub in (sh.SubShapes if sh.ShapeType == "Compound" else [sh]):
if cutvolume:
if sub.Volume < 0:
sub = sub.reversed() # Use reversed as sub is immutable.
c = sub.cut(cutvolume)
s = sub.section(cutface)
try:
wires = DraftGeomUtils.findWires(s.Edges)
for w in wires:
f = Part.Face(w)
tmpSshapes.append(f)
except Part.OCCError:
# print "ArchView: unable to get a face"
tmpSshapes.append(s)
shapes.extend(c.SubShapes if c.ShapeType == "Compound" else [c])
if showHidden:
c = sub.cut(invcutvolume)
hshapes.extend(c.SubShapes if c.ShapeType == "Compound" else [c])
else:
shapes.append(sub)
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
elif hasattr(o, "ViewObject") and hasattr(o.ViewObject, "ShapeColor"):
return o.ViewObject.ShapeColor
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,
linespacing=None,
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 = [] # Only used for annotations.
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",
"Axis",
]:
if isOriented(o, cutplane):
drafts.append(o)
elif o.isDerivedFrom("App::DocumentObjectGroup"):
# These will have been expanded by getSectionData already
pass
else:
nonspaces.append(o)
if Draft.getType(o.getLinkedObject()) == "Window": # To support Link of Windows(Doors)
windows.append(o)
objs = nonspaces
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 = params.get_param_arch("CutLineThickness")
svgCutLineWidth = str(scaledLineWidth * st) + "px"
yt = params.get_param_arch("SymbolLineThickness")
svgSymbolLineWidth = str(linewidth * yt)
hiddenPattern = params.get_param_arch("archHiddenPattern")
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 showFill or 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
import WorkingPlane
wp = WorkingPlane.PlaneBase()
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.align_to_placement(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] # Unused
# invcutvolume = source.Proxy.shapecache[5] # Unused
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 TechDraw module
import TechDraw
import Part
if vshapes:
baseshape = Part.makeCompound(vshapes)
style = {"stroke": "SVGLINECOLOR", "stroke-width": "SVGLINEWIDTH"}
svgcache += TechDraw.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 += TechDraw.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 += TechDraw.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,
linespacing=linespacing,
direction=direction,
color=lineColor,
techdraw=techdraw,
rotation=rotation,
override=False,
)
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,
linespacing=linespacing,
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 and BoundBoxValid(cutface.BoundBox):
cutwindows = [
w.Name
for w in windows
if BoundBoxValid(w.Shape.BoundBox) and w.Shape.BoundBox.intersect(cutface.BoundBox)
]
if windows:
sh = []
for w in windows:
if w.Name in cutwindows:
wlo = w.getLinkedObject() # To support Link of Windows(Doors)
if hasattr(wlo, "SymbolPlan") and wlo.SymbolPlan:
if not hasattr(wlo.Proxy, "sshapes"):
wlo.Proxy.execute(wlo)
if hasattr(wlo.Proxy, "sshapes") and wlo.Proxy.sshapes:
c = Part.makeCompound(wlo.Proxy.sshapes)
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,
linespacing=linespacing,
fillstyle="none",
direction=direction,
color=lineColor,
techdraw=techdraw,
rotation=rotation,
)
if not techdraw:
svg += "</g>"
return svg
def BoundBoxValid(boundBox) -> bool:
"""Return true if boundBox has a non-zero volume"""
return boundBox.XLength > 0 and boundBox.YLength > 0 and boundBox.ZLength > 0
def getDXF(obj):
"""Return a DXF representation from a TechDraw view."""
allOn = getattr(obj, "AllOn", True)
showHidden = getattr(obj, "ShowHidden", False)
result = []
import TechDraw
import 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)
objs = [
obj
for obj in objs
if (
not obj.isDerivedFrom("Part::Part2DObject")
and Draft.getType(obj)
not in ["BezCurve", "BSpline", "Wire", "Annotation", "Dimension", "Space"]
)
]
vshapes, hshapes, sshapes, cutface, cutvolume, invcutvolume = getCutShapes(
objs, cutplane, onlySolids, clip, False, showHidden
)
if vshapes:
result.append(TechDraw.projectToDXF(Part.makeCompound(vshapes), direction))
if sshapes:
result.append(TechDraw.projectToDXF(Part.makeCompound(sshapes), direction))
if hshapes:
result.append(TechDraw.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)
# disable animations to prevent a crash:
# https://github.com/FreeCAD/FreeCAD/issues/24929
view_window.setAnimationEnabled(False)
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.PlaneBase()
wp.align_to_point_and_axis_svg(Vector(0, 0, 0), cutplane.normalAt(0, 0), 0)
p = wp.get_local_coords(markervec)
orlength = FreeCAD.Vector(p.x, p.y, 0).Length
marker = re.findall(r"<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.get_local_coords(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(r"<line x1=.*?stroke=\"\#ffffff\".*?\/>", "", svg, count=1)
# remove background rectangle
svg = re.sub(r"<path.*?>", "", svg, count=1, flags=re.MULTILINE | re.DOTALL)
# set face color to white
if facecolor:
res = re.findall(r"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(
r"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(r"<\?xml.*?>", "", svg, flags=re.MULTILINE | re.DOTALL)
svg = re.sub(r"<svg.*?>", "", svg, flags=re.MULTILINE | re.DOTALL)
svg = re.sub(r"<\/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 _SectionPlane:
"A section plane object"
def __init__(self, obj):
obj.Proxy = self
self.Type = "SectionPlane"
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"),
locked=True,
)
if not "Shape" in pl:
obj.addProperty(
"Part::PropertyPartShape",
"Shape",
"SectionPlane",
QT_TRANSLATE_NOOP("App::Property", "The shape of this object"),
locked=True,
)
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.",
),
locked=True,
)
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.",
),
locked=True,
)
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.",
),
locked=True,
)
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.",
),
locked=True,
)
obj.UseMaterialColorForFill = False
if not "Depth" in pl:
obj.addProperty(
"App::PropertyLength",
"Depth",
"SectionPlane",
QT_TRANSLATE_NOOP(
"App::Property",
"Geometry further than this value will be cut off. Keep zero for unlimited.",
),
locked=True,
)
def onDocumentRestored(self, obj):
self.setProperties(obj)
def execute(self, obj):
import math
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
if not l:
l = 1
if not h:
h = 1
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...
# Apply the object's placement to the new plane first.
p.Placement = obj.Placement
# Now, check if the resulting plane's normal matches the placement's intended direction.
# This robustly handles all rotation angles and potential OCC inconsistencies.
target_normal = obj.Placement.Rotation.multVec(FreeCAD.Vector(0, 0, 1))
if p.normalAt(0, 0).getAngle(target_normal) > math.pi / 2:
p.reverse()
obj.Shape = p
self.svgcache = None
self.shapecache = None
def getNormal(self, obj):
return obj.Shape.Faces[0].normalAt(0, 0)
def dumps(self):
return None
def loads(self, state):
self.Type = "SectionPlane"
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"),
locked=True,
)
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"),
locked=True,
)
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"),
locked=True,
)
vobj.ArrowSize = 50
if not "Transparency" in pl:
vobj.addProperty(
"App::PropertyPercent",
"Transparency",
"SectionPlane",
QT_TRANSLATE_NOOP("App::Property", "The transparency of this object"),
locked=True,
)
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"),
locked=True,
)
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"),
locked=True,
)
if not "LineColor" in pl:
vobj.addProperty(
"App::PropertyColor",
"LineColor",
"SectionPlane",
QT_TRANSLATE_NOOP("App::Property", "The color of this object"),
locked=True,
)
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"),
locked=True,
)
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)",
),
locked=True,
)
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"),
locked=True,
)
if not "FontName" in pl:
vobj.addProperty(
"App::PropertyFont",
"FontName",
"SectionPlane",
QT_TRANSLATE_NOOP("App::Property", "The name of the font"),
locked=True,
)
vobj.FontName = params.get_param("textfont")
if not "FontSize" in pl:
vobj.addProperty(
"App::PropertyLength",
"FontSize",
"SectionPlane",
QT_TRANSLATE_NOOP("App::Property", "The size of the text font"),
locked=True,
)
vobj.FontSize = params.get_param("textheight") * params.get_param(
"DefaultAnnoScaleMultiplier"
)
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()
import PartGui # Required for "SoBrepEdgeSet" (because a SectionPlane is not a Part::FeaturePython object).
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):
vobj = obj.ViewObject
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(vobj, "DisplayLength")
# Defer the clipping plane update until after the current event
# loop finishes. This ensures the scene graph has been updated with the
# new placement before we try to recalculate the clip plane.
if vobj and hasattr(vobj, "CutView") and vobj.CutView:
from PySide import QtCore
# We use a lambda to pass the vobj argument to the delayed function.
QtCore.QTimer.singleShot(0, lambda: self.refreshCutView(vobj))
elif prop == "Label":
if hasattr(obj.ViewObject, "ShowLabel") and obj.ViewObject.ShowLabel:
self.txt.string = obj.Label
return
def refreshCutView(self, vobj):
"""
Forces a refresh of the SoClipPlane by toggling the CutView property.
This is called with a delay to ensure the object's placement is up-to-date.
"""
if vobj and hasattr(vobj, "CutView") and vobj.CutView:
vobj.CutView = False
vobj.CutView = True
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"]:
# for IFC objects: propagate to the object
if prop in ["DisplayLength", "DisplayHeight"]:
if hasattr(vobj.Object.Proxy, "onChanged"):
vobj.Object.Proxy.onChanged(vobj.Object, prop)
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
and hasattr(FreeCADGui.ActiveDocument.ActiveView, "getSceneGraph")
):
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 dumps(self):
return None
def loads(self, state):
return None
def setEdit(self, vobj, mode):
if mode != 0:
return None
taskd = SectionPlaneTaskPanel()
taskd.obj = vobj.Object
taskd.update()
FreeCADGui.Control.showDialog(taskd)
return True
def unsetEdit(self, vobj, mode):
if mode != 0:
return None
FreeCADGui.Control.closeDialog()
return True
def doubleClicked(self, vobj):
self.edit()
return True
def setupContextMenu(self, vobj, menu):
if FreeCADGui.activeWorkbench().name() != "BIMWorkbench":
return
actionEdit = QtGui.QAction(translate("Arch", "Edit"), menu)
QtCore.QObject.connect(actionEdit, QtCore.SIGNAL("triggered()"), self.edit)
menu.addAction(actionEdit)
actionToggleCutview = QtGui.QAction(
QtGui.QIcon(":/icons/Draft_Edit.svg"), translate("Arch", "Toggle Cutview"), menu
)
actionToggleCutview.triggered.connect(lambda: self.toggleCutview(vobj))
menu.addAction(actionToggleCutview)
def edit(self):
FreeCADGui.ActiveDocument.setEdit(self.Object, 0)
def toggleCutview(self, vobj):
vobj.CutView = not vobj.CutView
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
# Create the first box for object scope
self.scope_widget = QtGui.QWidget()
scope_layout = QtGui.QGridLayout(self.scope_widget)
self.title = QtGui.QLabel(self.scope_widget)
scope_layout.addWidget(self.title, 0, 0, 1, 2)
# tree
self.tree = QtGui.QTreeWidget(self.scope_widget)
scope_layout.addWidget(self.tree, 1, 0, 1, 2)
self.tree.setColumnCount(1)
self.tree.header().hide()
# add / remove buttons
self.addButton = QtGui.QPushButton(self.scope_widget)
self.addButton.setIcon(QtGui.QIcon(":/icons/Arch_Add.svg"))
scope_layout.addWidget(self.addButton, 2, 0, 1, 1)
self.delButton = QtGui.QPushButton(self.scope_widget)
self.delButton.setIcon(QtGui.QIcon(":/icons/Arch_Remove.svg"))
scope_layout.addWidget(self.delButton, 2, 1, 1, 1)
self.delButton.setEnabled(False)
# Create the second box for tools
self.tools_widget = QtGui.QWidget()
tools_layout = QtGui.QVBoxLayout(self.tools_widget)
# Cut View toggle button
self.cutViewButton = QtGui.QPushButton(self.tools_widget)
self.cutViewButton.setIcon(QtGui.QIcon(":/icons/Arch_CutPlane.svg"))
self.cutViewButton.setObjectName("cutViewButton")
self.cutViewButton.setCheckable(True)
QtCore.QObject.connect(
self.cutViewButton, QtCore.SIGNAL("toggled(bool)"), self.toggleCutView
)
tools_layout.addWidget(self.cutViewButton)
# rotate / resize buttons
self.rotation_label = QtGui.QLabel(self.tools_widget)
tools_layout.addWidget(self.rotation_label)
rotation_layout = QtGui.QHBoxLayout()
self.rotateXButton = QtGui.QPushButton(self.tools_widget)
self.rotateYButton = QtGui.QPushButton(self.tools_widget)
self.rotateZButton = QtGui.QPushButton(self.tools_widget)
rotation_layout.addWidget(self.rotateXButton)
rotation_layout.addWidget(self.rotateYButton)
rotation_layout.addWidget(self.rotateZButton)
tools_layout.addLayout(rotation_layout)
size_pos_layout = QtGui.QHBoxLayout()
self.resizeButton = QtGui.QPushButton(self.tools_widget)
self.recenterButton = QtGui.QPushButton(self.tools_widget)
size_pos_layout.addWidget(self.resizeButton)
size_pos_layout.addWidget(self.recenterButton)
tools_layout.addLayout(size_pos_layout)
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.form = [self.scope_widget, self.tools_widget]
self.update()
def isAllowedAlterSelection(self):
return True
def isAllowedAlterView(self):
return True
def getStandardButtons(self):
return QtGui.QDialogButtonBox.Close
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))
if self.obj.ViewObject and hasattr(self.obj.ViewObject, "CutView"):
self.cutViewButton.setChecked(self.obj.ViewObject.CutView)
self.retranslateUi()
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(
"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
local_axis = self.obj.Placement.Rotation.multVec(axis)
face.rotate(self.obj.Placement.Base, local_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 reject(self):
FreeCAD.ActiveDocument.recompute()
FreeCADGui.ActiveDocument.resetEdit()
return True
def toggleCutView(self, checked):
if self.obj and self.obj.ViewObject and hasattr(self.obj.ViewObject, "CutView"):
self.obj.ViewObject.CutView = checked
def retranslateUi(self):
self.scope_widget.setWindowTitle(QtGui.QApplication.translate("Arch", "Scope", None))
self.tools_widget.setWindowTitle(
QtGui.QApplication.translate("Arch", "Placement and Visuals", None)
)
self.title.setText(
QtGui.QApplication.translate("Arch", "Objects seen by this section plane", None)
)
self.delButton.setText(QtGui.QApplication.translate("Arch", "Remove", None))
self.delButton.setToolTip(
QtGui.QApplication.translate(
"Arch", "Removes highlighted objects from the list above", None
)
)
self.addButton.setText(QtGui.QApplication.translate("Arch", "Add Selected", None))
self.addButton.setToolTip(
QtGui.QApplication.translate(
"Arch", "Adds selected objects to the scope of this section plane", None
)
)
self.cutViewButton.setText(QtGui.QApplication.translate("Arch", "Cut View", None))
self.cutViewButton.setToolTip(
QtGui.QApplication.translate(
"Arch",
"Creates a live cut in the 3D view, hiding geometry on one side of the plane to see inside your model",
None,
)
)
self.rotation_label.setText(QtGui.QApplication.translate("Arch", "Rotate by 90°", None))
self.rotateXButton.setText(QtGui.QApplication.translate("Arch", "Rotate X", None))
self.rotateXButton.setToolTip(
QtGui.QApplication.translate("Arch", "Rotates the plane around its local X-axis", None)
)
self.rotateYButton.setText(QtGui.QApplication.translate("Arch", "Rotate Y", None))
self.rotateYButton.setToolTip(
QtGui.QApplication.translate("Arch", "Rotates the plane around its local Y-axis", None)
)
self.rotateZButton.setText(QtGui.QApplication.translate("Arch", "Rotate Z", None))
self.rotateZButton.setToolTip(
QtGui.QApplication.translate("Arch", "Rotates the plane around its local Z-axis", None)
)
self.resizeButton.setText(QtGui.QApplication.translate("Arch", "Resize to Fit", 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", "Recenter Plane", None))
self.recenterButton.setToolTip(
QtGui.QApplication.translate(
"Arch", "Centers the plane on the objects in the list above", None
)
)