Macro cross section: Difference between revisions

From FreeCAD Documentation
m (minor)
(Marked this version for translation)
 
(8 intermediate revisions by 3 users not shown)
Line 4: Line 4:
{{Macro
{{Macro
|Name=Cross_section
|Name=Cross_section
|Icon=Macro_cross_section.png
|Description=Macro to display a cross-section of the objects in the scene: the cross-section plane can be moved by a slider bar.
|Description=Macro to display a cross-section of the objects in the scene: the cross-section plane can be moved by a slider bar.
|Author=aleph0
|Author=aleph0
|Version=0.07
|Version=0.10
|Date=2017-09-17
|Date=2020-12-19
|FCVersion=0.15 and above
|Download=[https://www.freecadweb.org/wiki/images/f/f5/Macro_cross_section.png ToolBar Icon]
}}
}}


Line 48: Line 51:
Changes by Gift include.
Changes by Gift include.


Changes by duke24 include.
<!--T:12-->

Changes by g.becu include.

Changes by Chris_G include.

ToolBar Icon [[Image:Macro_cross_section.png]]


'''Macro cross section.FCMacro'''
'''Macro cross section.FCMacro'''


{{Code|code=
{{MacroCode|code=
# -*- coding: utf-8 -*-
# -*- coding: utf-8 -*-
"""
"""
Line 94: Line 103:
__title__ = "Cross-Section"
__title__ = "Cross-Section"
__author__ = "Aleph0"
__author__ = "Aleph0"
__version__ = "00.07"
__version__ = "00.10"
__date__ = "17/09/2017"
__date__ = "19/12/2020"


#"29/01/2016" "10/06/2016" "29/09/2016 * 2" "17/08/2017" "06/09/2017" "17/09/2017"
# "29/01/2016" "10/06/2016" "29/09/2016 * 2" "17/08/2017" "06/09/2017" "17/09/2017" "04/07/2019" "31/08/2019" "19/12/2020"


# Upgrade:
# Upgrade:
# 17/08/2017 by Sam #https://forum.freecadweb.org/viewtopic.php?f=28&t=15084&start=10#p187030
# 17/08/2017 by Sam #https://forum.freecadweb.org/viewtopic.php?f=28&t=15084&start=10#p187030
# 06/09/2017 17/09/2017 by Gift #https://www.forum.freecadweb.org/viewtopic.php?f=13&t=24130
# 06/09/2017 17/09/2017 by Gift #https://www.forum.freecadweb.org/viewtopic.php?f=13&t=24130
# 04/07/2019 by duke24 replace tab to space #https://forum.freecadweb.org/viewtopic.php?f=13&t=37449
# 31/08/2019 by g.becu adding line 334 #https://forum.freecadweb.org/viewtopic.php?f=28&t=15084&start=20#p330351
# 19/12/2020 by Chris_G : exlude App.Part from the list of input objects + many code style fixes (PEP8)
# See forum : https://forum.freecadweb.org/viewtopic.php?f=22&t=53423

__Comment__ = "Dynamic cross section viewer"
__Comment__ = "Dynamic cross section viewer"
__Wiki__ = "http://www.freecadweb.org/wiki/index.php?title=Macro_cross_section"
__Wiki__ = "http://www.freecadweb.org/wiki/index.php?title=Macro_cross_section"
__Help__ = "see first few lines of macro text"
__Help__ = "see first few lines of macro text"
__Status__ = "stable"
__Status__ = "stable"
__Requires__ = "freecad 0.15"
__Requires__ = "freecad 0.15"

# OS: Ubuntu 14.04.3 LTS
# OS: Ubuntu 14.04.3 LTS # OS: Windows 10
# Word size of OS: 64-bit
# Word size of OS: 64-bit # Word size of OS: 64-bit
# Word size of FreeCAD: 64-bit
# Word size of FreeCAD: 64-bit # Word size of FreeCAD: 64-bit
# Version: 0.15.4671 (Git)
# Version: 0.15.4671 (Git) # Version: 0.19.17171 (Git)
# Branch: releases/FreeCAD-0-15
# Branch: releases/FreeCAD-0-15 # Build type: Release
# Hash: 244b3aef360841646cbfe80a1b225c8b39c8380c
# Hash: 244b3aef360841646cbfe80a1b225c8b39c8380c# Branch: master
# Python version: 2.7.6
# Python version: 2.7.6 # Hash: d19470a9711ea604f3ca6c93e46afadf64d5bb87
# Qt version: 4.8.6
# Qt version: 4.8.6 # Python version: 3.6.6
# Coin version: 4.0.0a
# Coin version: 4.0.0a # Qt version: 5.6.2
# OCC version: 6.8.0.oce-0.17
# OCC version: 6.8.0.oce-0.17 # Coin version: 4.0.0a
# # OCC version: 7.3.0

import PySide
import PySide
from PySide import QtCore, QtGui
from PySide import QtCore, QtGui
import FreeCAD as App
import FreeCADGui
import FreeCADGui
import Part
import Part
Line 133: Line 147:
def _fromUtf8(s):
def _fromUtf8(s):
return s
return s

try:
try:
_encoding = QtGui.QApplication.UnicodeUTF8
_encoding = QtGui.QApplication.UnicodeUTF8

def _translate(context, text, disambig):
def _translate(context, text, disambig):
return QtGui.QApplication.translate(context, text, disambig, _encoding)
return QtGui.QApplication.translate(context, text, disambig, _encoding)
Line 141: Line 156:
def _translate(context, text, disambig):
def _translate(context, text, disambig):
return QtGui.QApplication.translate(context, text, disambig)
return QtGui.QApplication.translate(context, text, disambig)


width = 350 # width of our window
width = 350 # width of our window
height = 310 # height of our window
height = 310 # height of our window
global FreeCADRootWindow # main window before we start
global FreeCADRootWindow # main window before we start


# This is the interactive window that the macro creates
# This is the interactive window that the macro creates
# Its main function is to have a close box and tidy up when closed
# Its main function is to have a close box and tidy up when closed
class CrossSectionWindow(PySide.QtGui.QMainWindow):
class CrossSectionWindow(PySide.QtGui.QMainWindow):
# automagically called when the window is created
# automagically called when the window is created
def __init__(self):
def __init__(self):
Line 157: Line 172:
self.setWindowTitle(_translate(
self.setWindowTitle(_translate(
"MainWindow", "Cross-section", None))
"MainWindow", "Cross-section", None))

# Create and set the window's widget, which does all the work
# Create and set the window's widget, which does all the work
self.child = CrossSection(self)
self.child = CrossSection(self)
self.setCentralWidget(self.child)
self.setCentralWidget(self.child)
self.child.initUI(self)
self.child.initUI(self)
if self.child.startup_failed :
if self.child.startup_failed:
self.destroy()
self.destroy()
else:
else:
Line 169: Line 184:
self.child.show()
self.child.show()
self.show()
self.show()

# User closed the window, tidy up
# User closed the window, tidy up
def closeEvent(self, event) :
def closeEvent(self, event):
self.child.restoreObjects()
self.child.restoreObjects()

# Set a sensible default position for the window
# Set a sensible default position for the window
# With FreeCAD's default layout, this will be over the docking area
# With FreeCAD's default layout, this will be over the docking area
# so it will not obscure the 3D view
# so it will not obscure the 3D view
def setPosition(self, parent) :
def setPosition(self, parent):
geom = parent.geometry()
geom = parent.geometry()
xpos = geom.left() + 50
xpos = geom.left() + 50
Line 183: Line 198:
self.setGeometry(xpos, ypos, width, height)
self.setGeometry(xpos, ypos, width, height)
self.setFixedSize(width, height)
self.setFixedSize(width, height)


# This is the widget which does almost all of the work
# This is the widget which does almost all of the work
# Widgets don't have close boxes, so closing is dealt with in CrossSectionWindow
# Widgets don't have close boxes, so closing is dealt with in CrossSectionWindow
class CrossSection(PySide.QtGui.QWidget):
class CrossSection(PySide.QtGui.QWidget):
# Lay out the interactive elements
# Lay out the interactive elements
def initUI(self, parent):
def initUI(self, parent):
self.parent = parent
self.parent = parent

font = QtGui.QFont()
font = QtGui.QFont()
font.setFamily("Times New Roman")
font.setFamily("Times New Roman")
Line 198: Line 211:
font.setWeight(10)
font.setWeight(10)
font.setBold(True)
font.setBold(True)
self.hideObjects()
self.hideObjects()


margin = 10
margin = 10
alw = 20 # axis label width
alw = 20 # axis label width
sbp = margin + alw + margin # spin boxes to right of axis labels
sbp = margin + alw + margin # spin boxes to right of axis labels
slw = width - 2 * margin # width of slider and progress bar
slw = width - 2 * margin # width of slider and progress bar
ypos = margin
ypos = margin

self.setObjectName(_fromUtf8("CrossSection"))
self.setObjectName(_fromUtf8("CrossSection"))

# The cross-section plane is defined by its normal
# The cross-section plane is defined by its normal
# The length of the normal is immaterial as long as it is nonzero
# The length of the normal is immaterial as long as it is nonzero
Line 220: Line 232:
"Cross-section is taken perpendicular to this axis", None))
"Cross-section is taken perpendicular to this axis", None))
ypos = ypos + 30
ypos = ypos + 30

self.label_2 = QtGui.QLabel(self)
self.label_2 = QtGui.QLabel(self)
self.label_2.setGeometry(QtCore.QRect(margin, ypos, alw, 25))
self.label_2.setGeometry(QtCore.QRect(margin, ypos, alw, 25))
self.label_2.setObjectName(_fromUtf8("label_2"))
self.label_2.setObjectName(_fromUtf8("label_2"))
self.label_2.setText(_translate("MainWindow", "X", None))
self.label_2.setText(_translate("MainWindow", "X", None))

self.doubleSpinBox_X = QtGui.QDoubleSpinBox(self)
self.doubleSpinBox_X = QtGui.QDoubleSpinBox(self)
self.doubleSpinBox_X.setGeometry(QtCore.QRect(sbp, ypos, 62, 25))
self.doubleSpinBox_X.setGeometry(QtCore.QRect(sbp, ypos, 62, 25))
Line 234: Line 246:
self.doubleSpinBox_X.setObjectName(_fromUtf8("doubleSpinBox_X"))
self.doubleSpinBox_X.setObjectName(_fromUtf8("doubleSpinBox_X"))
self.doubleSpinBox_X.valueChanged.connect(
self.doubleSpinBox_X.valueChanged.connect(
self.on_doubleSpinBox_X_valueChanged)
self.on_doubleSpinBox_X_valueChanged)
self.doubleSpinBox_X.setToolTip(_translate(
self.doubleSpinBox_X.setToolTip(_translate(
"MainWindow", "Sliding axis X", None))
"MainWindow", "Sliding axis X", None))
ypos = ypos + 30
ypos = ypos + 30

self.label_3 = QtGui.QLabel(self)
self.label_3 = QtGui.QLabel(self)
self.label_3.setGeometry(QtCore.QRect(margin, ypos, alw, 25))
self.label_3.setGeometry(QtCore.QRect(margin, ypos, alw, 25))
self.label_3.setObjectName(_fromUtf8("label_3"))
self.label_3.setObjectName(_fromUtf8("label_3"))
self.label_3.setText(_translate("MainWindow", "Y", None))
self.label_3.setText(_translate("MainWindow", "Y", None))

self.doubleSpinBox_Y = QtGui.QDoubleSpinBox(self)
self.doubleSpinBox_Y = QtGui.QDoubleSpinBox(self)
self.doubleSpinBox_Y.setGeometry(QtCore.QRect(sbp, ypos, 62, 25))
self.doubleSpinBox_Y.setGeometry(QtCore.QRect(sbp, ypos, 62, 25))
Line 252: Line 264:
self.doubleSpinBox_Y.setObjectName(_fromUtf8("doubleSpinBox_Y"))
self.doubleSpinBox_Y.setObjectName(_fromUtf8("doubleSpinBox_Y"))
self.doubleSpinBox_Y.valueChanged.connect(
self.doubleSpinBox_Y.valueChanged.connect(
self.on_doubleSpinBox_Y_valueChanged)
self.on_doubleSpinBox_Y_valueChanged)
self.doubleSpinBox_Y.setToolTip(_translate(
self.doubleSpinBox_Y.setToolTip(_translate(
"MainWindow", "Sliding axis Y", None))
"MainWindow", "Sliding axis Y", None))
ypos = ypos + 30
ypos = ypos + 30

self.label_4 = QtGui.QLabel(self)
self.label_4 = QtGui.QLabel(self)
self.label_4.setGeometry(QtCore.QRect(margin, ypos, alw, 25))
self.label_4.setGeometry(QtCore.QRect(margin, ypos, alw, 25))
self.label_4.setObjectName(_fromUtf8("label_4"))
self.label_4.setObjectName(_fromUtf8("label_4"))
self.label_4.setText(_translate("MainWindow", "Z", None))
self.label_4.setText(_translate("MainWindow", "Z", None))

self.doubleSpinBox_Z = QtGui.QDoubleSpinBox(self)
self.doubleSpinBox_Z = QtGui.QDoubleSpinBox(self)
self.doubleSpinBox_Z.setGeometry(QtCore.QRect(sbp, ypos, 62, 25))
self.doubleSpinBox_Z.setGeometry(QtCore.QRect(sbp, ypos, 62, 25))
Line 270: Line 282:
self.doubleSpinBox_Z.setObjectName(_fromUtf8("doubleSpinBox_Z"))
self.doubleSpinBox_Z.setObjectName(_fromUtf8("doubleSpinBox_Z"))
self.doubleSpinBox_Z.valueChanged.connect(
self.doubleSpinBox_Z.valueChanged.connect(
self.on_doubleSpinBox_Z_valueChanged)
self.on_doubleSpinBox_Z_valueChanged)
self.doubleSpinBox_Z.setToolTip(
self.doubleSpinBox_Z.setToolTip(
_translate("MainWindow", "Sliding axis Z", None))
_translate("MainWindow", "Sliding axis Z", None))
Line 283: Line 295:
"MainWindow", "Position along axis", None))
"MainWindow", "Position along axis", None))
ypos = ypos + 30
ypos = ypos + 30

self.horizontalSlider = QtGui.QSlider(self)
self.horizontalSlider = QtGui.QSlider(self)
self.horizontalSlider.setGeometry(QtCore.QRect(margin, ypos, slw-50, 20))
self.horizontalSlider.setGeometry(QtCore.QRect(margin, ypos, slw - 50, 20))
self.horizontalSlider.setOrientation(QtCore.Qt.Horizontal)
self.horizontalSlider.setOrientation(QtCore.Qt.Horizontal)
self.horizontalSlider.setInvertedAppearance(False)
self.horizontalSlider.setInvertedAppearance(False)
Line 293: Line 305:
self.horizontalSlider.setToolTip(_translate(
self.horizontalSlider.setToolTip(_translate(
"MainWindow", "Slide to move cross-section along axis", None))
"MainWindow", "Slide to move cross-section along axis", None))

# must be called after setValue()
# must be called after setValue()
self.horizontalSlider.valueChanged.connect(self.on_horizontal_slider)
self.horizontalSlider.valueChanged.connect(self.on_horizontal_slider)
ypos = ypos + 30
ypos = ypos + 30

self.progressBar_1 = QtGui.QProgressBar(self)
self.progressBar_1 = QtGui.QProgressBar(self)
self.progressBar_1.setGeometry(QtCore.QRect(margin, ypos, slw-50, 23))
self.progressBar_1.setGeometry(QtCore.QRect(margin, ypos, slw - 50, 23))
self.progressBar_1.setValue(int(self.fraction * 100.0))
self.progressBar_1.setValue(int(self.fraction * 100.0))
self.progressBar_1.setOrientation(QtCore.Qt.Horizontal)
self.progressBar_1.setOrientation(QtCore.Qt.Horizontal)
Line 307: Line 319:
"MainWindow", "Percent position of cross-section", None))
"MainWindow", "Percent position of cross-section", None))
ypos = ypos + 40
ypos = ypos + 40

################################## add
################################## add
self.SpinBox_PBar = QtGui.QSpinBox(self)
self.SpinBox_PBar = QtGui.QSpinBox(self)
self.SpinBox_PBar.setGeometry(QtCore.QRect(sbp+255, ypos-40, 45, 23))
self.SpinBox_PBar.setGeometry(QtCore.QRect(sbp+255, ypos - 40, 45, 23))
self.SpinBox_PBar.setMinimum(0.0)
self.SpinBox_PBar.setMinimum(0.0)
self.SpinBox_PBar.setMaximum(100.0)
self.SpinBox_PBar.setMaximum(100.0)
Line 332: Line 344:
"MainWindow", "Make cross-section as wire outline", None))
"MainWindow", "Make cross-section as wire outline", None))
ypos = ypos + 30
ypos = ypos + 30

# Select the display format for the cross-section
# Select the display format for the cross-section
# This button shows a normal 3D display with everything above the
# This button shows a normal 3D display with everything above the
Line 346: Line 358:


##################################
##################################
#Keep the sectional view
# Keep the sectional view
self.CB_00 = QtGui.QCheckBox(self)
self.CB_00 = QtGui.QCheckBox(self)
self.CB_00.setText(_fromUtf8("Keep the sectional view"))
self.CB_00.setText(_fromUtf8("Keep the sectional view"))
Line 355: Line 367:
"MainWindow", "Keep the sectional view or erase", None))
"MainWindow", "Keep the sectional view or erase", None))
##################################
##################################



# Called at macro start up by initUI()
# Called at macro start up by initUI()
Line 366: Line 377:
self.axisZ = 1.0
self.axisZ = 1.0
self.cross_section_type = 1
self.cross_section_type = 1

# We must have something to do a cross-section on
# We must have something to do a cross-section on
if App.ActiveDocument is None :
if App.ActiveDocument is None:
QtGui.QMessageBox.warning(None, _translate("MainWindow",
QtGui.QMessageBox.warning(None, _translate("MainWindow",
"Cross-section", None),
"Cross-section", None),
Line 376: Line 387:
QtGui.QMessageBox.Cancel)
QtGui.QMessageBox.Cancel)
self.startup_failed = True
self.startup_failed = True
self.parent.destroy() # This will close the window
self.parent.destroy() # This will close the window
else :
else:

# Make a list of the user's objects
# Make a list of the user's objects
# WARNING!!
# WARNING!!
# This list is persistent. We'll get confused if the user deletes or
# This list is persistent. We'll get confused if the user deletes or
# adds objects while the macro is active
# adds objects while the macro is active
self.oblist = App.ActiveDocument.Objects
self.oblist = [] # App.ActiveDocument.Objects
for obj in App.ActiveDocument.Objects:
try:
obj.getPropertyByName("Shape")
self.oblist.append(obj)
except AttributeError:
pass
# self.oblist = [obj for obj in self.oblist if hasattr(obj,"Shape")]
# Create the cross-section object
# Create the cross-section object
self.cs = FreeCAD.ActiveDocument.addObject("Part::Feature",
self.cs = App.ActiveDocument.addObject("Part::Feature", "Generated__cross-section")
"Generated__cross-section")
self.OriginalVisibilities = list()
self.OriginalVisibilities = list()
self.xmin = 1000.0
self.xmin = 1000.0
Line 398: Line 414:
# for p in self.oblist: # stupid Python list has no length function
# for p in self.oblist: # stupid Python list has no length function
# n = n + 1
# n = n + 1
############################# Skip the <group object> ######################## # add
############################# Skip the <group object> ########################
b0 = []
b0 = []
for x0 in self.oblist:
for x0 in self.oblist:
if str(x0) != "<group object>":
if str(x0) != "<group object>":
b0.append(x0)
b0.append(x0)
self.oblist = b0
self.oblist = b0
Line 412: Line 428:
vis = self.oblist[i].ViewObject.Visibility
vis = self.oblist[i].ViewObject.Visibility
self.OriginalVisibilities.append(vis)
self.OriginalVisibilities.append(vis)
if vis :
if vis:
if self.oblist[i].TypeId != str("Drawing::FeatureViewPython") : # add
if self.oblist[i].TypeId != str("Drawing::FeatureViewPython"): # add
self.oblist[i].ViewObject.Visibility=False
self.oblist[i].ViewObject.Visibility = False
b = self.oblist[i].Shape.BoundBox
b = self.oblist[i].Shape.BoundBox
if b.XMin < self.xmin :
if b.XMin < self.xmin:
self.xmin = b.XMin
self.xmin = b.XMin
if b.XMax > self.xmax :
if b.XMax > self.xmax:
self.xmax = b.XMax
self.xmax = b.XMax
if b.YMin < self.ymin :
if b.YMin < self.ymin:
self.ymin = b.YMin
self.ymin = b.YMin
if b.YMax > self.ymax :
if b.YMax > self.ymax:
self.ymax = b.YMax
self.ymax = b.YMax
if b.ZMin < self.zmin :
if b.ZMin < self.zmin:
self.zmin = b.ZMin
self.zmin = b.ZMin
if b.ZMax > self.zmax :
if b.ZMax > self.zmax:
self.zmax = b.ZMax
self.zmax = b.ZMax
# Moan and give up if there is nothing to cross-section
# Moan and give up if there is nothing to cross-section
if self.xmax <= self.xmin \
if self.xmax <= self.xmin \
or self.ymax <= self.ymin \
or self.ymax <= self.ymin \
or self.zmax <= self.zmin :
or self.zmax <= self.zmin:
QtGui.QMessageBox.warning(None, _translate("MainWindow",
QtGui.QMessageBox.warning(None, _translate("MainWindow",
"Cross-section", None),
"Cross-section", None),
Line 442: Line 458:
self.startup_failed = True
self.startup_failed = True
App.ActiveDocument.removeObject("Generated__cross_section")
App.ActiveDocument.removeObject("Generated__cross_section")
self.parent.destroy() # This will close the window
self.parent.destroy() # This will close the window
else :
else:
# Trigger initial display
# Trigger initial display
self.updateAxis()
self.updateAxis()

# Called when macro is closed
# Called when macro is closed
# Restore the original visibility of the user's objects
# Restore the original visibility of the user's objects
Line 453: Line 469:


n = 0
n = 0
for p in self.oblist: # stupid Python list has no length function
for p in self.oblist: # stupid Python list has no length function
n = n + 1
n = n + 1
for i in range(n):
for i in range(n):
self.oblist[i].ViewObject.Visibility = self.OriginalVisibilities[i]
self.oblist[i].ViewObject.Visibility = self.OriginalVisibilities[i]
try:
try:
if self.CB_00.isChecked() == False:
if self.CB_00.isChecked() is False:
App.ActiveDocument.removeObject("Generated__cross_section")
App.ActiveDocument.removeObject("Generated__cross_section")
except Exception:
except Exception:
None
None

# Something has changed, recalculate the cross-section
# Something has changed, recalculate the cross-section
def updateGui(self):
def updateGui(self):
# default to no visible cross-ection
# default to no visible cross-ection
self.cs.ViewObject.Visibility = False
self.cs.ViewObject.Visibility = False

# check if the sliding axis is invalid
# check if the sliding axis is invalid
if self.xdir == 0.0 and self.ydir == 0.0 and self.zdir == 0.0 :
if self.xdir == 0.0 and self.ydir == 0.0 and self.zdir == 0.0 :
Line 476: Line 492:
QtGui.QMessageBox.Ok,
QtGui.QMessageBox.Ok,
QtGui.QMessageBox.Ok)
QtGui.QMessageBox.Ok)
else :
else:

# First we compute the 0% and 100% positions on the sliding axis
# First we compute the 0% and 100% positions on the sliding axis
if self.xdir < 0.0 :
if self.xdir < 0.0:
x = self.xmax + self.fraction * self.xdir
x = self.xmax + self.fraction * self.xdir
else :
else:
x = self.xmin + self.fraction * self.xdir
x = self.xmin + self.fraction * self.xdir
if self.ydir < 0.0 :
if self.ydir < 0.0:
y = self.ymax + self.fraction * self.ydir
y = self.ymax + self.fraction * self.ydir
else :
else:
y = self.ymin + self.fraction * self.ydir
y = self.ymin + self.fraction * self.ydir
if self.zdir < 0.0 :
if self.zdir < 0.0:
z = self.zmax + self.fraction * self.zdir
z = self.zmax + self.fraction * self.zdir
else :
else:
z = self.zmin + self.fraction * self.zdir
z = self.zmin + self.fraction * self.zdir


Line 496: Line 512:


# Display the dimensions
# Display the dimensions
dimDep = "X "+str(x)+" mm , Y "+str(y)+" mm , Z "+str(z)+" mm"
dimDep = "X " + str(x) + " mm , Y " + str(y) + " mm , Z " + str(z) + " mm"
App.Console.PrintMessage(dimDep + "\n")
App.Console.PrintMessage(dimDep + "\n")



# Create a big enough disc in the cross-section plane
# Create a big enough disc in the cross-section plane
c = Part.makeCircle(r, Base.Vector(x, y, z),
c = Part.makeCircle(r, Base.Vector(x, y, z),
Base.Vector(self.xdir, self.ydir, self.zdir))
Base.Vector(self.xdir, self.ydir, self.zdir))
f = Part.Face(Part.Wire(c))
f = Part.Face(Part.Wire(c))
l = list() # list for cross-section parts
part_list = list() # list for cross-section parts
n = 0
n = 0
for p in self.oblist: # stupid Python list has no length function
for p in self.oblist: # stupid Python list has no length function
n = n + 1
n = n + 1
something = 0
something = 0
if self.cross_section_type == 1 :
if self.cross_section_type == 1:
# wire line cross-section
# wire line cross-section
for i in range(n):
for i in range(n):
if self.oblist[i].TypeId != str("Drawing::FeatureViewPython") : # add
if self.oblist[i].TypeId != str("Drawing::FeatureViewPython"): # add
if self.OriginalVisibilities[i] :
if self.OriginalVisibilities[i]:
ob = self.oblist[i].Shape.section(f)
ob = self.oblist[i].Shape.section(f)
# check for valid section part
# check for valid section part
if ob.BoundBox.XMin <= ob.BoundBox.XMax :
if ob.BoundBox.XMin <= ob.BoundBox.XMax:
l.append(ob)
part_list.append(ob)
something = 1
something = 1
else :
else:
# cut shape cross-section
# cut shape cross-section
# extrude our cross-ection disc into a cylinder
# extrude our cross-ection disc into a cylinder
cyl = f.extrude(Base.Vector(self.xdir, self.ydir, self.zdir))
cyl = f.extrude(Base.Vector(self.xdir, self.ydir, self.zdir))
doc = FreeCAD.ActiveDocument
doc = App.ActiveDocument
for i in range(n):
for i in range(n):
if self.oblist[i].TypeId != str("Drawing::FeatureViewPython") : # add
if self.oblist[i].TypeId != str("Drawing::FeatureViewPython"): # add
if self.OriginalVisibilities[i] :
if self.OriginalVisibilities[i]:
if (self.oblist[i].Shape.Volume <= 0.0):
if (self.oblist[i].Shape.Volume <= 0.0):
continue
continue
acut = doc.addObject("Part::Cut","Cut")
acut = doc.addObject("Part::Cut", "Cut")
abase = Draft.clone(self.oblist[i])
abase = Draft.clone(self.oblist[i])
abase.ViewObject.DiffuseColor = self.oblist[i].ViewObject.DiffuseColor
abase.ViewObject.DiffuseColor = self.oblist[i].ViewObject.DiffuseColor
abase.ViewObject.Transparency = self.oblist[i].ViewObject.Transparency
abase.ViewObject.Transparency = self.oblist[i].ViewObject.Transparency
doc.recompute()
doc.recompute()
atool = doc.addObject("Part::Feature")
atool = doc.addObject("Part::Feature")
atool.Shape = cyl
atool.Shape = cyl
atool.ViewObject.ShapeColor= self.oblist[i].ViewObject.ShapeColor #(0.25,0.57,0.35)
atool.ViewObject.ShapeColor = self.oblist[i].ViewObject.ShapeColor # (0.25,0.57,0.35)
atool.ViewObject.Transparency = self.oblist[i].ViewObject.Transparency
atool.ViewObject.Transparency = self.oblist[i].ViewObject.Transparency
doc.recompute()
doc.recompute()
acut.Base = abase
acut.Base = abase
acut.Tool = atool
acut.Tool = atool
acut.ViewObject.DiffuseColor = abase.ViewObject.DiffuseColor
acut.ViewObject.DiffuseColor = abase.ViewObject.DiffuseColor
doc.recompute()
doc.recompute()
if acut.Shape.isValid() and acut.Shape.Volume > 0.0 :
if acut.Shape.isValid() and acut.Shape.Volume > 0.0:
ob = doc.addObject("Part::Feature")
ob = doc.addObject("Part::Feature")
ob.Shape = acut.Shape
ob.Shape = acut.Shape
ob.ViewObject.DiffuseColor = acut.ViewObject.DiffuseColor
ob.ViewObject.DiffuseColor = acut.ViewObject.DiffuseColor
doc.recompute()
doc.recompute()
l.append(ob)
part_list.append(ob)
something = 2
something = 2
doc.removeObject(acut.Name)
doc.removeObject(acut.Name)
doc.recompute()
doc.recompute()
doc.removeObject(abase.Name)
doc.removeObject(abase.Name)
doc.recompute()
doc.recompute()
doc.removeObject(atool.Name)
doc.removeObject(atool.Name)
doc.recompute()
doc.recompute()


if something == 1 :
if something == 1:
s = Part.makeCompound(l)
s = Part.makeCompound(part_list)
if s.isValid() :
if s.isValid():
self.cs.Shape = s
self.cs.Shape = s
self.cs.ViewObject.Visibility = True
self.cs.ViewObject.Visibility = True
if something == 2 :
if something == 2:
feature = FreeCAD.ActiveDocument.addObject("Part::Compound","Compound")
feature = App.ActiveDocument.addObject("Part::Compound", "Compound")
feature.Links = l
feature.Links = part_list
FreeCAD.ActiveDocument.recompute()
App.ActiveDocument.recompute()
if feature.Shape.isValid() :
if feature.Shape.isValid():
self.cs.Shape = feature.Shape.copy()
self.cs.Shape = feature.Shape.copy()
self.cs.ViewObject.DiffuseColor = feature.ViewObject.DiffuseColor
self.cs.ViewObject.DiffuseColor = feature.ViewObject.DiffuseColor
self.cs.ViewObject.Visibility = True
self.cs.ViewObject.Visibility = True
FreeCAD.ActiveDocument.recompute()
App.ActiveDocument.recompute()
for od in l:
for od in part_list:
FreeCAD.ActiveDocument.removeObject(od.Name)
App.ActiveDocument.removeObject(od.Name)
FreeCAD.ActiveDocument.recompute()
App.ActiveDocument.recompute()
FreeCAD.ActiveDocument.removeObject(feature.Name)
App.ActiveDocument.removeObject(feature.Name)
FreeCADGui.updateGui()
FreeCADGui.updateGui()

# User changed axis vector
# User changed axis vector
# Compute a new axis vector whose length just covers the model
# Compute a new axis vector whose length just covers the model
def updateAxis(self):
def updateAxis(self):
ldir = 0.0
ldir = 0.0
if self.axisX != 0.0 :
if self.axisX != 0.0:
ldir = (self.xmax - self.xmin) / abs(self.axisX)
ldir = (self.xmax - self.xmin) / abs(self.axisX)
if self.axisY != 0.0 :
if self.axisY != 0.0:
ldir = max(ldir, (self.ymax - self.ymin) / abs(self.axisY))
ldir = max(ldir, (self.ymax - self.ymin) / abs(self.axisY))
if self.axisZ != 0.0 :
if self.axisZ != 0.0:
ldir = max(ldir, (self.zmax - self.zmin) / abs(self.axisZ))
ldir = max(ldir, (self.zmax - self.zmin) / abs(self.axisZ))
self.xdir = self.axisX * ldir
self.xdir = self.axisX * ldir
self.ydir = self.axisY * ldir
self.ydir = self.axisY * ldir
self.zdir = self.axisZ * ldir
self.zdir = self.axisZ * ldir
self.updateGui() # recalculate the cross-section
self.updateGui() # recalculate the cross-section

def on_horizontal_slider(self, val):
def on_horizontal_slider(self, val):
self.fraction = val / 100.0
self.fraction = val / 100.0
Line 597: Line 612:
self.SpinBox_PBar.setValue(val)
self.SpinBox_PBar.setValue(val)
self.updateGui()
self.updateGui()

def on_doubleSpinBox_X_valueChanged(self, val):
def on_doubleSpinBox_X_valueChanged(self, val):
self.axisX = val
self.axisX = val
self.updateAxis()
self.updateAxis()

def on_doubleSpinBox_Y_valueChanged(self, val):
def on_doubleSpinBox_Y_valueChanged(self, val):
self.axisY = val
self.axisY = val
self.updateAxis()
self.updateAxis()

def on_doubleSpinBox_Z_valueChanged(self, val):
def on_doubleSpinBox_Z_valueChanged(self, val):
self.axisZ = val
self.axisZ = val
Line 614: Line 629:
self.horizontalSlider.setValue(int(val))
self.horizontalSlider.setValue(int(val))
self.updateGui()
self.updateGui()


def onRadioButton(self, wasChecked) :
def onRadioButton(self, wasChecked):
if self.radioButton_1.isChecked() :
if self.radioButton_1.isChecked():
self.cross_section_type = 1
self.cross_section_type = 1
else :
else:
self.cross_section_type = 2
self.cross_section_type = 2
self.updateGui()
self.updateGui()

#######################################
#######################################


# Find FreeCAD's root window
# Find FreeCAD's root window
FreeCADRootWindow = Gui.getMainWindow()
FreeCADRootWindow = FreeCADGui.getMainWindow()

# Create the window and start it
# Create the window and start it
myWidget = CrossSectionWindow()
myWidget = CrossSectionWindow()

# The CrossSectionWindow will do all the work
# The CrossSectionWindow will do all the work

}}
}}
{{clear}}
{{clear}}
<translate>
<translate>

==Example== <!--T:16-->
==Example== <!--T:16-->
</translate>
</translate>
Line 645: Line 662:


==Version== <!--T:11-->
==Version== <!--T:11-->

<!--T:19-->
ver 00.10 19/12/2020 : upgrade by Chris_G fix to exclude App.Part from input objects, see [https://forum.freecadweb.org/viewtopic.php?f=22&t=53423 cross section macro does not work on bodies within a part container]

<!--T:18-->
ver 00.09 31/08/2019 : upgrade by g.becu adding line 334 #[https://forum.freecadweb.org/viewtopic.php?f=28&t=15084&start=20#p330351 Sezione Dinamica]

<!--T:17-->
ver 00.08 04/07/2019 : upgrade replace Tab to Space by duke24 [https://forum.freecadweb.org/viewtopic.php?f=13&t=37449 Macro cross_section update]


<!--T:13-->
<!--T:13-->
Line 654: Line 680:
<!--T:12-->
<!--T:12-->
ver 00.05 17/08/2017 : upgrade for 0.17 FreeCAD version by Sam see [https://forum.freecadweb.org/viewtopic.php?f=28&t=15084&start=10#p187030 Sezione Dinamica]
ver 00.05 17/08/2017 : upgrade for 0.17 FreeCAD version by Sam see [https://forum.freecadweb.org/viewtopic.php?f=28&t=15084&start=10#p187030 Sezione Dinamica]



</translate>
</translate>

Latest revision as of 18:13, 28 December 2020

Other languages:

Cross_section

Description
Macro to display a cross-section of the objects in the scene: the cross-section plane can be moved by a slider bar.

Macro version: 0.10
Last modified: 2020-12-19
FreeCAD version: 0.15 and above
Download: ToolBar Icon
Author: aleph0
Author
aleph0
Download
ToolBar Icon
Links
Macro Version
0.10
Date last modified
2020-12-19
FreeCAD Version(s)
0.15 and above
Default shortcut
None
See also
None

Description

Macro to display a cross-section of the objects in the scene: the cross-section plane can be moved by a slider bar.


Use

  • To run the macro, first download it from this site and install it in your macros directory,
  • then use the menu to call up your list of macros and double-click on this one.
  • It will display a default cross-section and pop up a window to enable you to set the cross-sectioning parameters.
  • Closing the pop-up window will restore the scene to its previous state before the macro was started.

LINKS

See forum

Look down right: "Keep the sectional view"

Script

Changes by Mario52 include.

Changes by Sam include.

Changes by Gift include.

Changes by duke24 include.

Changes by g.becu include.

Changes by Chris_G include.

ToolBar Icon

Macro cross section.FCMacro

# -*- coding: utf-8 -*-
"""
***************************************************************************
*                                                                         *
*   This widget makes an interactively moveable cross-section of the      *
*   currently visible objects in the currently active document. The       *
*   cross-sectioning plane is specified by its normal, and the cross-     *
*   section can be moved along that normal using a slider bar. It can     *
*   show the cross-section either as an outline or a view of the sliced   *
*   objects. To run the macro, first download it from this site and       *
*   install it in your macros directory, then use the menu to call up     *
*   your list of macros and double-click on this one. It will display     *
*   a default cross-section and pop up a window to enable you to set      *
*   the cross-sectioning parameters. Closing the pop-up window will       *
*   restore the scene to its previous state before the macro was          *
*   started.                                                              *
*                                                                         *
***************************************************************************
*   Copyright (c) 2016 Richard P. Parkins, M. A.                          *
*                                                                         *
*   This file is a supplement to the FreeCAD CAx development system.      *
*                                                                         *
*   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 software 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 macro; if not, write to the Free Software     *
*   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  *
*   USA                                                                   *
***************************************************************************
"""
__title__   = "Cross-Section"
__author__  = "Aleph0"
__version__ = "00.10"
__date__    = "19/12/2020"

# "29/01/2016" "10/06/2016" "29/09/2016 * 2" "17/08/2017" "06/09/2017" "17/09/2017" "04/07/2019" "31/08/2019" "19/12/2020"

# Upgrade:
# 17/08/2017 by Sam             #https://forum.freecadweb.org/viewtopic.php?f=28&t=15084&start=10#p187030
# 06/09/2017 17/09/2017 by Gift #https://www.forum.freecadweb.org/viewtopic.php?f=13&t=24130
# 04/07/2019 by duke24 replace tab to space #https://forum.freecadweb.org/viewtopic.php?f=13&t=37449
# 31/08/2019 by g.becu adding line 334 #https://forum.freecadweb.org/viewtopic.php?f=28&t=15084&start=20#p330351
# 19/12/2020 by Chris_G : exlude App.Part from the list of input objects + many code style fixes (PEP8)
#                         See forum : https://forum.freecadweb.org/viewtopic.php?f=22&t=53423

__Comment__ = "Dynamic cross section viewer"
__Wiki__ = "http://www.freecadweb.org/wiki/index.php?title=Macro_cross_section"
__Help__ = "see first few lines of macro text"
__Status__ = "stable"
__Requires__ = "freecad 0.15"

# OS: Ubuntu 14.04.3 LTS                        # OS: Windows 10
# Word size of OS: 64-bit                       # Word size of OS: 64-bit
# Word size of FreeCAD: 64-bit                  # Word size of FreeCAD: 64-bit
# Version: 0.15.4671 (Git)                      # Version: 0.19.17171 (Git)
# Branch: releases/FreeCAD-0-15                 # Build type: Release
# Hash: 244b3aef360841646cbfe80a1b225c8b39c8380c# Branch: master
# Python version: 2.7.6                         # Hash: d19470a9711ea604f3ca6c93e46afadf64d5bb87
# Qt version: 4.8.6                             # Python version: 3.6.6
# Coin version: 4.0.0a                          # Qt version: 5.6.2
# OCC version: 6.8.0.oce-0.17                   # Coin version: 4.0.0a
#                                               # OCC version: 7.3.0

import PySide
from PySide import QtCore, QtGui
import FreeCAD as App
import FreeCADGui
import Part
import Draft
from FreeCAD import Base

try:
    _fromUtf8 = QtCore.QString.fromUtf8
except AttributeError:
    def _fromUtf8(s):
        return s

try:
    _encoding = QtGui.QApplication.UnicodeUTF8

    def _translate(context, text, disambig):
        return QtGui.QApplication.translate(context, text, disambig, _encoding)
except AttributeError:
    def _translate(context, text, disambig):
        return QtGui.QApplication.translate(context, text, disambig)


width = 350  # width of our window
height = 310  # height of our window
global FreeCADRootWindow  # main window before we start


# This is the interactive window that the macro creates
# Its main function is to have a close box and tidy up when closed
class CrossSectionWindow(PySide.QtGui.QMainWindow):
    # automagically called when the window is created
    def __init__(self):
        super(CrossSectionWindow, self).__init__()
        self.setWindowFlags(PySide.QtCore.Qt.WindowStaysOnTopHint)
        self.setWindowTitle(_translate(
            "MainWindow", "Cross-section", None))

        # Create and set the window's widget, which does all the work
        self.child = CrossSection(self)
        self.setCentralWidget(self.child)
        self.child.initUI(self)
        if self.child.startup_failed:
            self.destroy()
        else:
            # Position our window relative to the FreeCAD root
            self.setPosition(FreeCADRootWindow)
            self.child.show()
            self.show()

    # User closed the window, tidy up
    def closeEvent(self, event):
        self.child.restoreObjects()

    # Set a sensible default position for the window
    # With FreeCAD's default layout, this will be over the docking area
    # so it will not obscure the 3D view
    def setPosition(self, parent):
        geom = parent.geometry()
        xpos = geom.left() + 50
        ypos = geom.center().y() - height / 2
        self.setGeometry(xpos, ypos, width, height)
        self.setFixedSize(width, height)


# This is the widget which does almost all of the work
# Widgets don't have close boxes, so closing is dealt with in CrossSectionWindow
class CrossSection(PySide.QtGui.QWidget):
    # Lay out the interactive elements
    def initUI(self, parent):
        self.parent = parent
        font = QtGui.QFont()
        font.setFamily("Times New Roman")
        font.setPointSize(10)
        font.setWeight(10)
        font.setBold(True)
        self.hideObjects()

        margin = 10
        alw = 20  # axis label width
        sbp = margin + alw + margin  # spin boxes to right of axis labels
        slw = width - 2 * margin  # width of slider and progress bar
        ypos = margin

        self.setObjectName(_fromUtf8("CrossSection"))

        # The cross-section plane is defined by its normal
        # The length of the normal is immaterial as long as it is nonzero
        self.label_1 = QtGui.QLabel(self)
        self.label_1.setGeometry(QtCore.QRect(margin, ypos, slw, 22))
        self.label_1.setObjectName(_fromUtf8("label_1"))
        self.label_1.setText(_translate(
            "MainWindow", "Sliding axis direction", None))
        self.label_1.setToolTip(_translate(
            "MainWindow",
            "Cross-section is taken perpendicular to this axis", None))
        ypos = ypos + 30

        self.label_2 = QtGui.QLabel(self)
        self.label_2.setGeometry(QtCore.QRect(margin, ypos, alw, 25))
        self.label_2.setObjectName(_fromUtf8("label_2"))
        self.label_2.setText(_translate("MainWindow", "X", None))

        self.doubleSpinBox_X = QtGui.QDoubleSpinBox(self)
        self.doubleSpinBox_X.setGeometry(QtCore.QRect(sbp, ypos, 62, 25))
        self.doubleSpinBox_X.setMinimum(-10000.0)
        self.doubleSpinBox_X.setMaximum(10000.0)
        self.doubleSpinBox_X.setValue(0.0)
        self.doubleSpinBox_X.setSingleStep(1)
        self.doubleSpinBox_X.setObjectName(_fromUtf8("doubleSpinBox_X"))
        self.doubleSpinBox_X.valueChanged.connect(
            self.on_doubleSpinBox_X_valueChanged)
        self.doubleSpinBox_X.setToolTip(_translate(
            "MainWindow", "Sliding axis X", None))
        ypos = ypos + 30

        self.label_3 = QtGui.QLabel(self)
        self.label_3.setGeometry(QtCore.QRect(margin, ypos, alw, 25))
        self.label_3.setObjectName(_fromUtf8("label_3"))
        self.label_3.setText(_translate("MainWindow", "Y", None))

        self.doubleSpinBox_Y = QtGui.QDoubleSpinBox(self)
        self.doubleSpinBox_Y.setGeometry(QtCore.QRect(sbp, ypos, 62, 25))
        self.doubleSpinBox_Y.setMinimum(-10000.0)
        self.doubleSpinBox_Y.setMaximum(10000.0)
        self.doubleSpinBox_Y.setValue(0.0)
        self.doubleSpinBox_Y.setSingleStep(1)
        self.doubleSpinBox_Y.setObjectName(_fromUtf8("doubleSpinBox_Y"))
        self.doubleSpinBox_Y.valueChanged.connect(
            self.on_doubleSpinBox_Y_valueChanged)
        self.doubleSpinBox_Y.setToolTip(_translate(
            "MainWindow", "Sliding axis Y", None))
        ypos = ypos + 30

        self.label_4 = QtGui.QLabel(self)
        self.label_4.setGeometry(QtCore.QRect(margin, ypos, alw, 25))
        self.label_4.setObjectName(_fromUtf8("label_4"))
        self.label_4.setText(_translate("MainWindow", "Z", None))

        self.doubleSpinBox_Z = QtGui.QDoubleSpinBox(self)
        self.doubleSpinBox_Z.setGeometry(QtCore.QRect(sbp, ypos, 62, 25))
        self.doubleSpinBox_Z.setMinimum(-10000.0)
        self.doubleSpinBox_Z.setMaximum(10000.0)
        self.doubleSpinBox_Z.setValue(1.0)
        self.doubleSpinBox_Z.setSingleStep(1)
        self.doubleSpinBox_Z.setObjectName(_fromUtf8("doubleSpinBox_Z"))
        self.doubleSpinBox_Z.valueChanged.connect(
            self.on_doubleSpinBox_Z_valueChanged)
        self.doubleSpinBox_Z.setToolTip(
            _translate("MainWindow", "Sliding axis Z", None))
        ypos = ypos + 40
        # Make the interactive slider control
        # As you move this slider with the mouse, the cross-section slides
        # through the model
        self.label_5 = QtGui.QLabel(self)
        self.label_5.setGeometry(QtCore.QRect(margin, ypos, slw, 22))
        self.label_5.setObjectName(_fromUtf8("label_5"))
        self.label_5.setText(_translate(
            "MainWindow", "Position along axis", None))
        ypos = ypos + 30

        self.horizontalSlider = QtGui.QSlider(self)
        self.horizontalSlider.setGeometry(QtCore.QRect(margin, ypos, slw - 50, 20))
        self.horizontalSlider.setOrientation(QtCore.Qt.Horizontal)
        self.horizontalSlider.setInvertedAppearance(False)
        self.horizontalSlider.setObjectName(_fromUtf8("horizontalSlider"))
        self.horizontalSlider.setRange(0, 100)
        self.horizontalSlider.setValue(int(self.fraction * 100.0))
        self.horizontalSlider.setToolTip(_translate(
            "MainWindow", "Slide to move cross-section along axis", None))

        # must be called after setValue()
        self.horizontalSlider.valueChanged.connect(self.on_horizontal_slider)
        ypos = ypos + 30

        self.progressBar_1 = QtGui.QProgressBar(self)
        self.progressBar_1.setGeometry(QtCore.QRect(margin, ypos, slw - 50, 23))
        self.progressBar_1.setValue(int(self.fraction * 100.0))
        self.progressBar_1.setOrientation(QtCore.Qt.Horizontal)
        self.progressBar_1.setAlignment(QtCore.Qt.AlignCenter)
        self.progressBar_1.setObjectName(_fromUtf8("progressBar_1"))
        self.progressBar_1.setToolTip(_translate(
            "MainWindow", "Percent position of cross-section", None))
        ypos = ypos + 40

##################################    add
        self.SpinBox_PBar = QtGui.QSpinBox(self)
        self.SpinBox_PBar.setGeometry(QtCore.QRect(sbp+255, ypos - 40, 45, 23))
        self.SpinBox_PBar.setMinimum(0.0)
        self.SpinBox_PBar.setMaximum(100.0)
        self.SpinBox_PBar.setValue(int(self.fraction * 100.0))
        self.SpinBox_PBar.setSingleStep(1)
        self.SpinBox_PBar.setObjectName(_fromUtf8("SpinBox_PBar"))
        self.SpinBox_PBar.valueChanged.connect(self.on_SpinBox_PBar_valueChanged)
        self.SpinBox_PBar.setToolTip(_translate(
            "MainWindow", "Sliding percent", None))
#################################    add

        # Select the display format for the cross-section
        # This button shows a wire line in the cross-section plane
        self.radioButton_1 = QtGui.QRadioButton(self)
        self.radioButton_1.setGeometry(QtCore.QRect(margin, ypos, slw, 20))
        self.radioButton_1.setText(_fromUtf8("Outline"))
        self.radioButton_1.setChecked(True)
        self.radioButton_1.toggled.connect(self.onRadioButton)
        self.radioButton_1.setObjectName(_fromUtf8("radioButton_1"))
        self.radioButton_1.setToolTip(_translate(
            "MainWindow", "Make cross-section as wire outline", None))
        ypos = ypos + 30

        # Select the display format for the cross-section
        # This button shows a normal 3D display with everything above the
        # cross-section plane cut away
        self.radioButton_2 = QtGui.QRadioButton(self)
        self.radioButton_2.setGeometry(QtCore.QRect(margin, ypos, slw, 20))
        self.radioButton_2.setText(_fromUtf8("Cut objects"))
        self.radioButton_2.setChecked(False)
        self.radioButton_2.toggled.connect(self.onRadioButton)
        self.radioButton_2.setObjectName(_fromUtf8("radioButton_2"))
        self.radioButton_2.setToolTip(_translate(
            "MainWindow", "Make cross-section as cut objects", None))

##################################
        # Keep the sectional view
        self.CB_00 = QtGui.QCheckBox(self)
        self.CB_00.setText(_fromUtf8("Keep the sectional view"))
        self.CB_00.setGeometry(QtCore.QRect(margin+168, ypos-30, slw, 20))
        self.CB_00.setObjectName(_fromUtf8("CB_00"))
        self.CB_00.setChecked(False)
        self.CB_00.setToolTip(_translate(
            "MainWindow", "Keep the sectional view or erase", None))
##################################

    # Called at macro start up by initUI()
    def hideObjects(self):
        # Initialise the cross-section state variables
        self.startup_failed = False
        self.fraction = 0.5
        self.axisX = 0.0
        self.axisY = 0.0
        self.axisZ = 1.0
        self.cross_section_type = 1

        # We must have something to do a cross-section on
        if App.ActiveDocument is None:
            QtGui.QMessageBox.warning(None, _translate("MainWindow",
                "Cross-section", None),
                _translate("MainWindow", "There is no Active Document\n" +
                    "Create one and run this macro again.", None),
                QtGui.QMessageBox.Cancel,
                QtGui.QMessageBox.Cancel)
            self.startup_failed = True
            self.parent.destroy()  # This will close the window
        else:

            # Make a list of the user's objects
            # WARNING!!
            # This list is persistent. We'll get confused if the user deletes or
            # adds objects while the macro is active
            self.oblist = []  # App.ActiveDocument.Objects
            for obj in App.ActiveDocument.Objects:
                try:
                    obj.getPropertyByName("Shape")
                    self.oblist.append(obj)
                except AttributeError:
                    pass
            # self.oblist = [obj for obj in self.oblist if hasattr(obj,"Shape")]
            # Create the cross-section object
            self.cs = App.ActiveDocument.addObject("Part::Feature", "Generated__cross-section")
            self.OriginalVisibilities = list()
            self.xmin = 1000.0
            self.xmax = -1000.0
            self.ymin = 1000.0
            self.ymax = -1000.0
            self.zmin = 1000.0
            self.zmax = -1000.0
#            n = 0
#            for p in self.oblist: # stupid Python list has no length function
#                n = n + 1
############################# Skip the <group object> ########################
            b0 = []
            for x0 in self.oblist:
                if str(x0) != "<group object>":
                    b0.append(x0)
            self.oblist = b0
            n = len(self.oblist)
###############################################################################
            # Make a list of the visible objects and make them invisible while
            # the macro is active
            # Also we compute the bounding box of the model
            for i in range(n):
                vis = self.oblist[i].ViewObject.Visibility
                self.OriginalVisibilities.append(vis)
                if vis:
                    if self.oblist[i].TypeId != str("Drawing::FeatureViewPython"):   # add
                        self.oblist[i].ViewObject.Visibility = False
                        b = self.oblist[i].Shape.BoundBox
                        if b.XMin < self.xmin:
                            self.xmin = b.XMin
                        if b.XMax > self.xmax:
                            self.xmax = b.XMax
                        if b.YMin < self.ymin:
                            self.ymin = b.YMin
                        if b.YMax > self.ymax:
                            self.ymax = b.YMax
                        if b.ZMin < self.zmin:
                            self.zmin = b.ZMin
                        if b.ZMax > self.zmax:
                            self.zmax = b.ZMax
            # Moan and give up if there is nothing to cross-section
            if self.xmax <= self.xmin \
                or self.ymax <= self.ymin \
                    or self.zmax <= self.zmin:
                QtGui.QMessageBox.warning(None, _translate("MainWindow",
                    "Cross-section", None),
                    _translate("MainWindow",
                    "There are no visible solid objects\n" +
                    "Create some and run this macro again.", None),
                    QtGui.QMessageBox.Cancel,
                    QtGui.QMessageBox.Cancel)
                self.restoreObjects()
                self.startup_failed = True
                App.ActiveDocument.removeObject("Generated__cross_section")
                self.parent.destroy()  # This will close the window
            else:
                # Trigger initial display
                self.updateAxis()

    # Called when macro is closed
    # Restore the original visibility of the user's objects
    # and destroy our cross-section object
    def restoreObjects(self):

        n = 0
        for p in self.oblist:  # stupid Python list has no length function
            n = n + 1
        for i in range(n):
            self.oblist[i].ViewObject.Visibility = self.OriginalVisibilities[i]
        try:
            if self.CB_00.isChecked() is False:
                App.ActiveDocument.removeObject("Generated__cross_section")
        except Exception:
            None

    # Something has changed, recalculate the cross-section
    def updateGui(self):
        # default to no visible cross-ection
        self.cs.ViewObject.Visibility = False

        # check if the sliding axis is invalid
        if self.xdir == 0.0 and self.ydir == 0.0 and self.zdir == 0.0 :
            QtGui.QMessageBox.warning(None, _translate("MainWindow",
                "Cross-section", None),
                _translate("MainWindow", "The sliding axis has zero length\n" +
                    "Please set a valid axis.", None),
                QtGui.QMessageBox.Ok,
                QtGui.QMessageBox.Ok)
        else:

            # First we compute the 0% and 100% positions on the sliding axis
            if self.xdir < 0.0:
                x = self.xmax + self.fraction * self.xdir
            else:
                x = self.xmin + self.fraction * self.xdir
            if self.ydir < 0.0:
                y = self.ymax + self.fraction * self.ydir
            else:
                y = self.ymin + self.fraction * self.ydir
            if self.zdir < 0.0:
                z = self.zmax + self.fraction * self.zdir
            else:
                z = self.zmin + self.fraction * self.zdir

            r = 2 * max(self.xmax - self.xmin, self.ymax - self.ymin,
                        self.zmax - self.xmin)

            # Display the dimensions
            dimDep = "X " + str(x) + " mm , Y " + str(y) + " mm , Z " + str(z) + " mm"
            App.Console.PrintMessage(dimDep + "\n")

            # Create a big enough disc in the cross-section plane
            c = Part.makeCircle(r, Base.Vector(x, y, z),
                                Base.Vector(self.xdir, self.ydir, self.zdir))
            f = Part.Face(Part.Wire(c))
            part_list = list()  # list for cross-section parts
            n = 0
            for p in self.oblist:  # stupid Python list has no length function
                n = n + 1
            something = 0
            if self.cross_section_type == 1:
                # wire line cross-section
                for i in range(n):
                    if self.oblist[i].TypeId != str("Drawing::FeatureViewPython"):  # add
                        if self.OriginalVisibilities[i]:
                            ob = self.oblist[i].Shape.section(f)
                            # check for valid section part
                            if ob.BoundBox.XMin <= ob.BoundBox.XMax:
                                part_list.append(ob)
                                something = 1
            else:
                # cut shape cross-section
                # extrude our cross-ection disc into a cylinder
                cyl = f.extrude(Base.Vector(self.xdir, self.ydir, self.zdir))
                doc = App.ActiveDocument
                for i in range(n):
                    if self.oblist[i].TypeId != str("Drawing::FeatureViewPython"):  # add
                        if self.OriginalVisibilities[i]:
                            if (self.oblist[i].Shape.Volume <= 0.0):
                                continue
                            acut = doc.addObject("Part::Cut", "Cut")
                            abase = Draft.clone(self.oblist[i])
                            abase.ViewObject.DiffuseColor = self.oblist[i].ViewObject.DiffuseColor
                            abase.ViewObject.Transparency = self.oblist[i].ViewObject.Transparency
                            doc.recompute()
                            atool = doc.addObject("Part::Feature")
                            atool.Shape = cyl
                            atool.ViewObject.ShapeColor = self.oblist[i].ViewObject.ShapeColor  # (0.25,0.57,0.35)
                            atool.ViewObject.Transparency = self.oblist[i].ViewObject.Transparency
                            doc.recompute()
                            acut.Base = abase
                            acut.Tool = atool
                            acut.ViewObject.DiffuseColor = abase.ViewObject.DiffuseColor
                            doc.recompute()
                            if acut.Shape.isValid() and acut.Shape.Volume > 0.0:
                                ob = doc.addObject("Part::Feature")
                                ob.Shape = acut.Shape
                                ob.ViewObject.DiffuseColor = acut.ViewObject.DiffuseColor
                                doc.recompute()
                                part_list.append(ob)
                                something = 2
                            doc.removeObject(acut.Name)
                            doc.recompute()
                            doc.removeObject(abase.Name)
                            doc.recompute()
                            doc.removeObject(atool.Name)
                            doc.recompute()

            if something == 1:
                s = Part.makeCompound(part_list)
                if s.isValid():
                    self.cs.Shape = s
                    self.cs.ViewObject.Visibility = True
            if something == 2:
                feature = App.ActiveDocument.addObject("Part::Compound", "Compound")
                feature.Links = part_list
                App.ActiveDocument.recompute()
                if feature.Shape.isValid():
                    self.cs.Shape = feature.Shape.copy()
                    self.cs.ViewObject.DiffuseColor = feature.ViewObject.DiffuseColor
                    self.cs.ViewObject.Visibility = True
                    App.ActiveDocument.recompute()
                for od in part_list:
                    App.ActiveDocument.removeObject(od.Name)
                    App.ActiveDocument.recompute()
                App.ActiveDocument.removeObject(feature.Name)
            FreeCADGui.updateGui()

    # User changed axis vector
    # Compute a new axis vector whose length just covers the model
    def updateAxis(self):
        ldir = 0.0
        if self.axisX != 0.0:
            ldir = (self.xmax - self.xmin) / abs(self.axisX)
        if self.axisY != 0.0:
            ldir = max(ldir, (self.ymax - self.ymin) / abs(self.axisY))
        if self.axisZ != 0.0:
            ldir = max(ldir, (self.zmax - self.zmin) / abs(self.axisZ))
        self.xdir = self.axisX * ldir
        self.ydir = self.axisY * ldir
        self.zdir = self.axisZ * ldir
        self.updateGui()  # recalculate the cross-section

    def on_horizontal_slider(self, val):
        self.fraction = val / 100.0
        self.progressBar_1.setValue(val)
        self.SpinBox_PBar.setValue(val)
        self.updateGui()

    def on_doubleSpinBox_X_valueChanged(self, val):
        self.axisX = val
        self.updateAxis()

    def on_doubleSpinBox_Y_valueChanged(self, val):
        self.axisY = val
        self.updateAxis()

    def on_doubleSpinBox_Z_valueChanged(self, val):
        self.axisZ = val
        self.updateAxis()

    def on_SpinBox_PBar_valueChanged(self, val):
        self.progressBar_1.setValue(val)
        self.horizontalSlider.setValue(int(val))
        self.updateGui()

    def onRadioButton(self, wasChecked):
        if self.radioButton_1.isChecked():
            self.cross_section_type = 1
        else:
            self.cross_section_type = 2
        self.updateGui()

#######################################


# Find FreeCAD's root window
FreeCADRootWindow = FreeCADGui.getMainWindow()

# Create the window and start it
myWidget = CrossSectionWindow()

# The CrossSectionWindow will do all the work

Example

Links

The forum discussion Posting a new macro

Version

ver 00.10 19/12/2020 : upgrade by Chris_G fix to exclude App.Part from input objects, see cross section macro does not work on bodies within a part container

ver 00.09 31/08/2019 : upgrade by g.becu adding line 334 #Sezione Dinamica

ver 00.08 04/07/2019 : upgrade replace Tab to Space by duke24 Macro cross_section update

ver 00.07 17/09/2017 : upgrade multiple objects with different colors by Gift

ver 00.06 06/09/2017 : upgrade by Gift see Optischer Schnitt durch Baugruppe, z.B. für Ventilgehäuse accept the multiple objects with different colors

ver 00.05 17/08/2017 : upgrade for 0.17 FreeCAD version by Sam see Sezione Dinamica