# -*- coding: utf-8 -*-
# Author: Ruslan Krenzler.
# Date: 09 February 2018
# Create a corner-fitting.
import math
import csv
import os.path
from PySide import QtCore, QtGui
import FreeCAD
import Spreadsheet
import Sketcher
import Part
tu = FreeCAD.Units.parseQuantity
def GetMacroPath():
param = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Macro")
return param.GetString("MacroPath","")
# This is the path to the dimensions table.
CSV_TABLE_PATH = GetMacroPath()+"/outer-corner.csv"
# It must contain unique values in the column "Name" and also, dimensions listened below.
DIMENSIONS_USED = ["G", "H", "M", "POD", "PID"]
def nestedObjects(group):
res = []
if group.OutList == []:
res.append(group)
else:
# Append children first.
for o in group.OutList:
res += nestedObjects(o)
res.append(group)
return res
def toSolid(document, part, name):
"""Convert object to a solid.
Basically those are commands, which FreeCAD runs when user converts a part to a solid.
"""
s = part.Shape.Faces
s = Part.Solid(Part.Shell(s))
o = document.addObject("Part::Feature", name)
o.Label=name
o.Shape=s
return o
class Error(Exception):
"""Base class for exceptions in this module."""
def __init__(self, message):
super(Error, self).__init__(message)
class UnplausibleDimensions(Error):
"""Exception raised when dimensions are unplausible. For example if
outer diameter is larger than the iner one.
Attributes:
message -- explanation of the error
"""
def __init__(self, message):
super(UnplausibleDimensions, self).__init__(message)
class OuterCorner:
def __init__(self, document):
self.document = document
self.G = tu("2 in")
self.H = tu("3 in")
self.M = tu("3 in")
self.POD = tu("2 in")
self.PID = tu("1 in")
def checkDimensions(self):
if not ( self.POD > tu("0 mm") and self.PID > tu("0 mm") ):
raise UnplausibleDimensions("Pipe dimensions must be positive. They are POD=%s and PID=%s instead"%(self.POD, self.PID))
if not (self.M > self.POD and self.POD > self.PID):
raise UnplausibleDimensions("Outer diameter M %s must be larger than outer pipe POD %s diamter. ",
"Outer pipe diameter POD %s must be larger than inner pipe diameter PID %s"%(self.M, self.POD, self.PID))
if not (self.H > self.G):
raise UnplausibleDimensions("Length G %s must be larger than H %s."%(self.G, self.H))
def createPrimitiveCorner(self, L, D):
"""Create corner consisting of two cylinder along x-,y- and y axis and a ball in the center."""
x_cylinder = self.document.addObject("Part::Cylinder","XCynlider")
x_cylinder.Radius = D/2
x_cylinder.Height = L
x_cylinder.Placement = App.Placement(App.Vector(0,0,0), App.Rotation(App.Vector(0,1,0),90), App.Vector(0,0,0))
y_cylinder = self.document.addObject("Part::Cylinder","YCynlider")
y_cylinder.Radius = D/2
y_cylinder.Height = L
y_cylinder.Placement = App.Placement(App.Vector(0,0,0), App.Rotation(App.Vector(1,0,0),-90), App.Vector(0,0,0))
z_cylinder = self.document.addObject("Part::Cylinder","ZCynlider")
z_cylinder.Radius = D/2
z_cylinder.Height = L
sphere = self.document.addObject("Part::Sphere","Sphere")
sphere.Radius = D/2
fusion = self.document.addObject("Part::MultiFuse","Fusion")
fusion.Shapes = [x_cylinder,y_cylinder,z_cylinder,sphere]
return fusion
def addSockets(self, fusion):
"""Add socket cylinders to the fusion."""
x_socket = self.document.addObject("Part::Cylinder","XSocket")
x_socket.Radius = self.POD / 2
x_socket.Height = self.H - self.G
x_socket.Placement = App.Placement(App.Vector(self.G, 0,0), App.Rotation(App.Vector(0,1,0),90), App.Vector(0,0,0))
y_socket = self.document.addObject("Part::Cylinder","YSocket")
y_socket.Radius = self.POD / 2
y_socket.Height = self.H - self.G
y_socket.Placement = App.Placement(App.Vector(0, self.G,0), App.Rotation(App.Vector(1,0,0),-90), App.Vector(0,0,0))
z_socket = self.document.addObject("Part::Cylinder","ZSocket")
z_socket.Radius = self.POD / 2
z_socket.Height = self.H - self.G
z_socket.Placement.Base = App.Vector(0, 0, self.G)
fusion.Shapes = fusion.Shapes + [x_socket, y_socket, z_socket] # fusion.Shapes.append does not work.
return fusion
def createOuterPart(self):
return self.createPrimitiveCorner(self.H, self.M)
def createInnerPart(self):
return self.createPrimitiveCorner(self.H, self.PID)
def create(self, convertToSolid):
self.checkDimensions()
outer = self.createOuterPart()
inner = self.createInnerPart()
inner = self.addSockets(inner)
# Remove inner part of the sockets.
corner = self.document.addObject("Part::Cut","Cut")
corner.Base = outer
corner.Tool = inner
if convertToSolid:
# Before making a solid, recompute documents. Otherwise there will be
# s = Part.Solid(Part.Shell(s))
#
To construct a part, only these dimensions are used: G, H, M, PID, and POD. All other dimensions are used for inromation.
", None, QtGui.QApplication.UnicodeUTF8)) def initTable(self): # Read table data from CSV self.model = PartTableModel(self.table.headers, self.table.data) self.tableViewParts.setModel(self.model) def getSelectedPartName(self): sel = form.tableViewParts.selectionModel() if sel.isSelected: if len(sel.selectedRows())> 0: rowIndex = sel.selectedRows()[0].row() return self.model.getPartName(rowIndex) return None def selectPartByName(self, partName): """Select first row with a part with a name partName.""" if partName is not None: row_i = self.model.getPartRowIndex(partName) if row_i >= 0: self.tableViewParts.selectRow(row_i) def accept(self): """User clicked OK""" # Update active document. If there is none, show a warning message and do nothing. document = App.activeDocument() if document is not None: # Get suitable row from the the table. partName = self.getSelectedPartName() createSolid = self.checkBoxCreateSolid.isChecked() if partName is not None: corner = OuterCornerFromTable(document, self.table) corner.create(partName, createSolid) document.recompute() # Save user input for the next dialog call. self.saveInput() # Call parent class. super(MainDialog, self).accept() else: msgBox = QtGui.QMessageBox() msgBox.setText("Select part") msgBox.exec_() else: text = "I have not found any active document were I can create a corner fitting.\n"\ "Use menu File->New to create a new document first, "\ "then try to create the corner fitting again." msgBox = QtGui.QMessageBox(QtGui.QMessageBox.Warning, "Creating of the corner fitting failed.", text) msgBox.exec_() def saveInput(self): """Store user input for the next run.""" settings = QtCore.QSettings(MainDialog.QSETTINGS_APPLICATION, MainDialog.QSETTINGS_NAME) check = self.checkBoxCreateSolid.checkState() settings.setValue("checkBoxCreateSolid", int(check)) settings.setValue("LastSelectedPartName", self.getSelectedPartName()) settings.sync() def restoreInput(self): settings = QtCore.QSettings(MainDialog.QSETTINGS_APPLICATION, MainDialog.QSETTINGS_NAME) checkState = QtCore.Qt.CheckState(int(settings.value("checkBoxCreateSolid"))) self.checkBoxCreateSolid.setCheckState(checkState) self.selectPartByName(settings.value("LastSelectedPartName")) # Before working with macros, try to load the dimension table. def GuiCheckTable(): # Check if the CSV file exists. if os.path.isfile(CSV_TABLE_PATH) == False: text = "This macro requires %s but this file does not exist."%(CSV_TABLE_PATH) msgBox = QtGui.QMessageBox(QtGui.QMessageBox.Warning, "Creating of the corner failed.", text) msgBox.exec_() exit(1) # Error print("Trying to load CSV file with dimensions: %s"%CSV_TABLE_PATH) table = CsvTable(DIMENSIONS_USED) table.load(CSV_TABLE_PATH) if table.hasValidData == False: text = 'Invalid %s.\n'\ 'It must contain columns %s.'%(CSV_TABLE_PATH, ", ".join(DIMENSIONS_USED)) msgBox = QtGui.QMessageBox(QtGui.QMessageBox.Warning, "Creating of the corner failed.", text) msgBox.exec_() exit(1) # Error return table # Test macros. def TestCorner(): document = App.activeDocument() corner = OuterCorner(document) corner.create(True) document.recompute() def TestTable(): document = App.activeDocument() table = CsvTable(DIMENSIONS_USED) table.load(CSV_TABLE_PATH) corner = OuterCornerFromTable(document, table) for i in range(0, len(table.data)): print("Selecting row %d"%i) partName = table.getPartName(i) print("Creating part %s"%partName) corner.create(partName, True) document.recompute() #TestCorner() #TestTable() table = GuiCheckTable() # Open a CSV file, check its content, and return it as a CsvTable object. form = MainDialog(table)