├── OsePiping ├── __init__.py ├── Modification.py ├── TeeGui.py ├── BushingGui.py ├── SweepElbowGui.py ├── CornerGui.py ├── ElbowGui.py ├── CrossGui.py ├── CouplingGui.py ├── PipingGui.py ├── PipeGui.py ├── Port.py ├── Piping.py ├── Pipe.py ├── FlCorner.py ├── FlBushing.py ├── FlCross.py ├── Corner.py ├── FlSweepElbow.py ├── FlCoupling.py └── FlElbow.py ├── doc ├── pipe-gui.jpg ├── cross-calculations.png ├── elbow-calculations.png ├── tee-calculations.png ├── bushing-calculations.png ├── corner-calculations.png ├── workbench-screenshot.png ├── coupling-calculations.png ├── create-cross-screenshot.png ├── create-elbow-screenshot.png ├── create-pipe-screenshot.png ├── create-tee-screenshot.png ├── create-bushing-screenshot.png ├── create-corner-screenshot.png ├── create-tee-cad-screenshot.png ├── sweep-elbow-calculations.png ├── create-corner-cad-screenshot.png ├── create-coupling-screenshot.png ├── create-cross-cad-screenshot.png ├── create-elbow-cad-screenshot.png ├── create-pipe-cad-screenshot.png ├── create-bushing-cad-screenshot.png ├── create-coupling-cad-screenshot.png ├── create-sweep-elbow-screenshot.png ├── create-flamingo-pipe-screenshot.png ├── create-sweep-elbow-cad-screenshot.png └── adjust-ports.svg ├── Resources ├── images │ ├── pipe-dimensions.png │ ├── tee-dimensions.png │ ├── corner-dimensions.png │ ├── cross-dimensions.png │ ├── elbow-dimensions.png │ ├── bushing-dimensions.png │ ├── coupling-dimensions.png │ └── sweep-elbow-dimensions.png └── icons │ ├── Workbench.svg │ ├── OSE_Piping_workbench_icon.svg │ ├── CreatePipe.svg │ ├── CreateBushing.svg │ ├── CreateTee.svg │ └── MoveAround.svg ├── tables ├── README.md ├── corner.csv ├── pipe.csv ├── sweep-elbow.csv └── cross.csv ├── setup.cfg ├── CHANGELOG.md ├── OsePipingBase.py ├── README.md ├── COPYRIGHT.template ├── .gitignore ├── InitGui.py ├── ui ├── create-part.ui └── create-pipe.ui └── LICENSE /OsePiping/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /doc/pipe-gui.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkrenzler/ose-piping-workbench/HEAD/doc/pipe-gui.jpg -------------------------------------------------------------------------------- /doc/cross-calculations.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkrenzler/ose-piping-workbench/HEAD/doc/cross-calculations.png -------------------------------------------------------------------------------- /doc/elbow-calculations.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkrenzler/ose-piping-workbench/HEAD/doc/elbow-calculations.png -------------------------------------------------------------------------------- /doc/tee-calculations.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkrenzler/ose-piping-workbench/HEAD/doc/tee-calculations.png -------------------------------------------------------------------------------- /doc/bushing-calculations.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkrenzler/ose-piping-workbench/HEAD/doc/bushing-calculations.png -------------------------------------------------------------------------------- /doc/corner-calculations.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkrenzler/ose-piping-workbench/HEAD/doc/corner-calculations.png -------------------------------------------------------------------------------- /doc/workbench-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkrenzler/ose-piping-workbench/HEAD/doc/workbench-screenshot.png -------------------------------------------------------------------------------- /doc/coupling-calculations.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkrenzler/ose-piping-workbench/HEAD/doc/coupling-calculations.png -------------------------------------------------------------------------------- /doc/create-cross-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkrenzler/ose-piping-workbench/HEAD/doc/create-cross-screenshot.png -------------------------------------------------------------------------------- /doc/create-elbow-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkrenzler/ose-piping-workbench/HEAD/doc/create-elbow-screenshot.png -------------------------------------------------------------------------------- /doc/create-pipe-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkrenzler/ose-piping-workbench/HEAD/doc/create-pipe-screenshot.png -------------------------------------------------------------------------------- /doc/create-tee-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkrenzler/ose-piping-workbench/HEAD/doc/create-tee-screenshot.png -------------------------------------------------------------------------------- /doc/create-bushing-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkrenzler/ose-piping-workbench/HEAD/doc/create-bushing-screenshot.png -------------------------------------------------------------------------------- /doc/create-corner-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkrenzler/ose-piping-workbench/HEAD/doc/create-corner-screenshot.png -------------------------------------------------------------------------------- /doc/create-tee-cad-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkrenzler/ose-piping-workbench/HEAD/doc/create-tee-cad-screenshot.png -------------------------------------------------------------------------------- /doc/sweep-elbow-calculations.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkrenzler/ose-piping-workbench/HEAD/doc/sweep-elbow-calculations.png -------------------------------------------------------------------------------- /Resources/images/pipe-dimensions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkrenzler/ose-piping-workbench/HEAD/Resources/images/pipe-dimensions.png -------------------------------------------------------------------------------- /Resources/images/tee-dimensions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkrenzler/ose-piping-workbench/HEAD/Resources/images/tee-dimensions.png -------------------------------------------------------------------------------- /doc/create-corner-cad-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkrenzler/ose-piping-workbench/HEAD/doc/create-corner-cad-screenshot.png -------------------------------------------------------------------------------- /doc/create-coupling-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkrenzler/ose-piping-workbench/HEAD/doc/create-coupling-screenshot.png -------------------------------------------------------------------------------- /doc/create-cross-cad-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkrenzler/ose-piping-workbench/HEAD/doc/create-cross-cad-screenshot.png -------------------------------------------------------------------------------- /doc/create-elbow-cad-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkrenzler/ose-piping-workbench/HEAD/doc/create-elbow-cad-screenshot.png -------------------------------------------------------------------------------- /doc/create-pipe-cad-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkrenzler/ose-piping-workbench/HEAD/doc/create-pipe-cad-screenshot.png -------------------------------------------------------------------------------- /Resources/images/corner-dimensions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkrenzler/ose-piping-workbench/HEAD/Resources/images/corner-dimensions.png -------------------------------------------------------------------------------- /Resources/images/cross-dimensions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkrenzler/ose-piping-workbench/HEAD/Resources/images/cross-dimensions.png -------------------------------------------------------------------------------- /Resources/images/elbow-dimensions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkrenzler/ose-piping-workbench/HEAD/Resources/images/elbow-dimensions.png -------------------------------------------------------------------------------- /doc/create-bushing-cad-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkrenzler/ose-piping-workbench/HEAD/doc/create-bushing-cad-screenshot.png -------------------------------------------------------------------------------- /doc/create-coupling-cad-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkrenzler/ose-piping-workbench/HEAD/doc/create-coupling-cad-screenshot.png -------------------------------------------------------------------------------- /doc/create-sweep-elbow-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkrenzler/ose-piping-workbench/HEAD/doc/create-sweep-elbow-screenshot.png -------------------------------------------------------------------------------- /tables/README.md: -------------------------------------------------------------------------------- 1 | The dimensions for fittings where collected from publicly available data from Aetna-Plastics Corporation and Formufit. 2 | -------------------------------------------------------------------------------- /Resources/images/bushing-dimensions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkrenzler/ose-piping-workbench/HEAD/Resources/images/bushing-dimensions.png -------------------------------------------------------------------------------- /Resources/images/coupling-dimensions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkrenzler/ose-piping-workbench/HEAD/Resources/images/coupling-dimensions.png -------------------------------------------------------------------------------- /doc/create-flamingo-pipe-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkrenzler/ose-piping-workbench/HEAD/doc/create-flamingo-pipe-screenshot.png -------------------------------------------------------------------------------- /doc/create-sweep-elbow-cad-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkrenzler/ose-piping-workbench/HEAD/doc/create-sweep-elbow-cad-screenshot.png -------------------------------------------------------------------------------- /Resources/images/sweep-elbow-dimensions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkrenzler/ose-piping-workbench/HEAD/Resources/images/sweep-elbow-dimensions.png -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [flake8] 2 | max-line-length = 119 3 | ignore = 4 | # Missing doc strings. 5 | D100,D101,D102,D103 6 | # variable and function naming. 7 | N802, N803, N806 8 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [0.2.0] 2020-12-10 4 | 5 | ### Added 6 | - Support for Dodo-Workbench. 7 | ### Changed 8 | - If both, Dodo and Flamingo Workbenches, are present, the workbench uses Dodo. 9 | -------------------------------------------------------------------------------- /OsePipingBase.py: -------------------------------------------------------------------------------- 1 | # Initialilize some paths. 2 | import os 3 | 4 | __dir__ = os.path.dirname(__file__) 5 | ICON_PATH = os.path.join( __dir__, 'Resources/icons' ) 6 | IMAGE_PATH = os.path.join( __dir__, 'Resources/images' ) 7 | TABLE_PATH = os.path.join( __dir__, 'tables' ) 8 | UI_PATH = os.path.join( __dir__, 'ui' ) 9 | -------------------------------------------------------------------------------- /OsePiping/Modification.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Authors: Ruslan Krenzler 3 | # Date: 17 December 2020 4 | # Move Dodo/Flamingo fittings. 5 | # The name "Modification" is taken from the Draft-Workbench 6 | import FreeCAD 7 | import FreeCADGui 8 | import OsePiping.Port as Port 9 | 10 | 11 | def all_selected_parts_have_ports(): 12 | for sel in FreeCADGui.Selection.getSelectionEx(): 13 | if not Port.supportsAdvancedPort(sel.Object): 14 | return False 15 | return True 16 | -------------------------------------------------------------------------------- /tables/corner.csv: -------------------------------------------------------------------------------- 1 | PartNumber,PipeSize,PSize,Schedule,G,H,M,POD,PID,PThk,Approx. Wt. 2 | 413-005,"1/2""",DN15,40,1/2 in,1+1/4 in,1+3/32 in,0.84 in,0.602 in,0.109 in,0.06 lbs 3 | 413-007,"3/4""",DN20,40,9/16 in,1+9/16 in,1+5/16 in,1.05 in,0.804 in,0.113 in,0.10 lbs 4 | 413-010,"1""",DN25,40,13/16 in,1+7/8 in,1+5/8 in,1.315 in,1.029 in,0.133 in,0.17 lbs 5 | 413-015,"1-1/2""",DN40,40,1+1/32 in,2+11/32 in,2+1/4 in,1.9 in,1.59 in,0.145 in,0.32 lbs 6 | 413-020,"2""",DN50,40,1+5/16 in,2+5/8 in,2+3/4 in,2.375 in,2.047 in,0.154 in,0.47 lbs 7 | F0123WE,"1/2""",DN15,40,0.4965 in,1.2475 in,1.075 in,0.844 in,0.602 in,0.109 in,0.060 lbs 8 | F0343WE,"3/4""",DN20,40,0.6025 in,1.6035 in,1.2930 in,1.0620 in,0.804 in,0.113 in,0.119 lbs 9 | F0013WE,"1""",DN25,40,0.7865 in,2.0375 in,1.595 in,1.321 in,1.029 in,0.133 in,0.175 lbs 10 | F1143WE,"1-1/4""",DN32,40,0.8615 in,2.3675 in,1.9690 in,1.6600 in,1.36 in,0.14 in,0.275 lbs 11 | F1123WE,"1-1/2""",DN40,40,0.9505 in,2.4485 in,2.2150 in,1.9000 in,1.59 in,0.145 in,0.363 lbs 12 | F0023WE,"2""",DN50,40,1.3685 in,2.6805 in,2.7150 in,2.3770 in,2.047 in,0.154 in,0.406 lbs 13 | -------------------------------------------------------------------------------- /OsePiping/TeeGui.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Author: Ruslan Krenzler. 3 | # Date: 06 February 2018 4 | # Create a tee-fitting. 5 | 6 | import OsePiping.Tee as Tee 7 | import OsePiping.CreatePartGui as CreatePartGui 8 | import OsePiping.Piping as Piping 9 | 10 | 11 | class MainDialog(CreatePartGui.BaseDialog): 12 | def __init__(self, document, table): 13 | params = CreatePartGui.DialogParams() 14 | params.document = document 15 | params.table = table 16 | params.dialogTitle = "Create Tee" 17 | params.fittingType = "Tee" 18 | params.dimensionsPixmap = "tee-dimensions.png" 19 | params.explanationText = """

20 | Only dimensions used are: 21 | M, M1, M2, G, G1, G2, H, H1, H2, POD, POD1, POD2, PThk, PThk1, PThk2. 22 | All other dimensions are used for inromation. 23 |

""" 24 | params.settingsName = "tee user input" 25 | params.keyColumnName = "PartNumber" 26 | super(MainDialog, self).__init__(params) 27 | 28 | def createNewPart(self, document, table, partName, outputType): 29 | builder = Tee.TeeFromTable(self.params.document, self.params.table) 30 | part = builder.create(partName, outputType) 31 | if outputType == Piping.OUTPUT_TYPE_DODO_OR_FLAMINGO: 32 | self.moveFlamingoPartToSelection(document, part) 33 | return part 34 | 35 | 36 | def GuiCheckTable(): 37 | return CreatePartGui.GuiCheckTable(Tee.CSV_TABLE_PATH, Tee.DIMENSIONS_USED) 38 | -------------------------------------------------------------------------------- /OsePiping/BushingGui.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Author: Ruslan Krenzler. 3 | # Date: 27 January 2018 4 | # Create a bushing-fitting. 5 | 6 | import OsePiping.Bushing as Bushing 7 | import OsePiping.CreatePartGui as CreatePartGui 8 | import OsePiping.Piping as Piping 9 | 10 | 11 | class MainDialog(CreatePartGui.BaseDialog): 12 | def __init__(self, document, table): 13 | params = CreatePartGui.DialogParams() 14 | params.document = document 15 | params.table = table 16 | params.dialogTitle = "Create Bushing" 17 | params.fittingType = "Bushing" 18 | params.dimensionsPixmap = "bushing-dimensions.png" 19 | params.explanationText = """

20 | To construct a part, only these dimensions are used: 21 | L, N, POD, POD1 and PThk1. 22 | All other dimensions are used for inromation. 23 |

""" 24 | params.settingsName = "bushing user input" 25 | params.keyColumnName = "PartNumber" 26 | super(MainDialog, self).__init__(params) 27 | 28 | def createNewPart(self, document, table, partName, outputType): 29 | builder = Bushing.BushingFromTable(self.params.document, self.params.table) 30 | part = builder.create(partName, outputType) 31 | if outputType == Piping.OUTPUT_TYPE_DODO_OR_FLAMINGO: 32 | self.moveFlamingoPartToSelection(document, part) 33 | return part 34 | 35 | 36 | def GuiCheckTable(): 37 | return CreatePartGui.GuiCheckTable(Bushing.CSV_TABLE_PATH, Bushing.DIMENSIONS_USED) 38 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OSE piping workbench 2 | 3 | OSE Piping Workbench creates pipes and fittings. It is a part of Open Source Ecology and Open Source Ecology Germany. To use all its features install the Dodo-Workbench. 4 | 5 | ## Installation 6 | Use the FreeCAD built-in [Addon Manager](https://github.com/FreeCAD/FreeCAD-addons#1-builtin-addon-manager) to install this workbench. 7 | To start the Addon Manger select menu **Tools -> Addon Manager**. 8 | 9 | [See](https://www.freecadweb.org/wiki/How_to_install_additional_workbenches) 10 | 11 | ### Linux 12 | 13 | ```` 14 | $ mkdir ~/.FreeCAD/Mod 15 | $ cd ~/.FreeCAD/Mod 16 | $ git clone https://github.com/rkrenzler/ose-piping-workbench.git 17 | ```` 18 | 19 | # Screenshots # 20 | ![90°-elbow dialog](doc/workbench-screenshot.png) 21 | 22 | # Detailed documentation # 23 | https://wiki.freecadweb.org/OSE_Piping_Workbench 24 | 25 | # Optional dependencies 26 | To use all features install [Dodo-Workbench](https://wiki.freecadweb.org/Dodo_Workbench). It brings: 27 | 28 | * Changeable parameters of the pipes and fittings. 29 | * Convenient moving and connection of the parts. 30 | 31 | # Deprecated # 32 | If you still use [Flamingo-Workbench](https://wiki.freecadweb.org/Flamingo_Workbench), 33 | please install [Dodo-Workbench]. The support of Flamingo will be dropped in the future. 34 | 35 | # Troubleshooting # 36 | If you get an error message "module ... not found", try to remove all .pyc-files in the ose-piping module. Then restart FreeCAD. 37 | 38 | 39 | # License # 40 | 41 | GPLv3 (see LICENSE) 42 | 43 | -------------------------------------------------------------------------------- /OsePiping/SweepElbowGui.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Author: Ruslan Krenzler. 3 | # Date: 30 March February 2018 4 | # Create a sweepElbow-fitting. 5 | 6 | import OsePiping.SweepElbow as SweepElbow 7 | import OsePiping.CreatePartGui as CreatePartGui 8 | import OsePiping.Piping as Piping 9 | 10 | 11 | class MainDialog(CreatePartGui.BaseDialog): 12 | def __init__(self, document, table): 13 | params = CreatePartGui.DialogParams() 14 | params.document = document 15 | params.table = table 16 | params.dialogTitle = "Create Sweep Elbow" 17 | params.fittingType = "SweepElbow" 18 | params.dimensionsPixmap = "sweep-elbow-dimensions.png" 19 | params.explanationText = """

20 | Only dimensions used are: 21 | BendAngle, H, J, M POD, PThk. 22 | All other dimensions are used for inromation. 23 |

""" 24 | params.settingsName = "sweep elbow user input" 25 | params.keyColumnName = "PartNumber" 26 | super(MainDialog, self).__init__(params) 27 | 28 | def createNewPart(self, document, table, partName, outputType): 29 | builder = SweepElbow.SweepElbowFromTable( 30 | self.params.document, self.params.table) 31 | part = builder.create(partName, outputType) 32 | if outputType == Piping.OUTPUT_TYPE_DODO_OR_FLAMINGO: 33 | self.moveFlamingoPartToSelection(document, part) 34 | return part 35 | 36 | 37 | def GuiCheckTable(): 38 | return CreatePartGui.GuiCheckTable(SweepElbow.CSV_TABLE_PATH, SweepElbow.DIMENSIONS_USED) 39 | -------------------------------------------------------------------------------- /OsePiping/CornerGui.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Author: Ruslan Krenzler. 3 | # Date: 09 February 2018 4 | # Create a corner-fitting. 5 | 6 | import OsePiping.Corner as Corner 7 | import OsePiping.CreatePartGui as CreatePartGui 8 | import OsePiping.Piping as Piping 9 | 10 | 11 | class MainDialog(CreatePartGui.BaseDialog): 12 | def __init__(self, document, table): 13 | params = CreatePartGui.DialogParams() 14 | params.document = document 15 | params.table = table 16 | params.dialogTitle = "Create corner" 17 | params.selectionDialogTitle = "Select corner" 18 | params.fittingType = "Corner" 19 | params.dimensionsPixmap = "corner-dimensions.png" 20 | params.explanationText = """

21 | To construct a part, only these dimensions are used: 22 | G, H, M, POD and PThk. 23 | All other dimensions are used for inromation. 24 |

""" 25 | params.settingsName = "corner user input" 26 | params.keyColumnName = "PartNumber" 27 | super(MainDialog, self).__init__(params) 28 | 29 | def createNewPart(self, document, table, partName, outputType): 30 | builder = Corner.CornerFromTable(self.params.document, self.params.table) 31 | part = builder.create(partName, outputType) 32 | if outputType == Piping.OUTPUT_TYPE_DODO_OR_FLAMINGO: 33 | self.moveFlamingoPartToSelection(document, part) 34 | return part 35 | 36 | 37 | def GuiCheckTable(): 38 | return CreatePartGui.GuiCheckTable(Corner.CSV_TABLE_PATH, Corner.DIMENSIONS_USED) 39 | -------------------------------------------------------------------------------- /OsePiping/ElbowGui.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Author: Ruslan Krenzler. 3 | # Date: 16 December 2017 4 | # Create a elbow-fitting. 5 | 6 | import OsePiping.Elbow as Elbow 7 | import OsePiping.CreatePartGui as CreatePartGui 8 | import OsePiping.Piping as Piping 9 | 10 | 11 | class MainDialog(CreatePartGui.BaseDialog): 12 | def __init__(self, document, table): 13 | params = CreatePartGui.DialogParams() 14 | params.document = document 15 | params.table = table 16 | params.dialogTitle = "Create Elbow" 17 | params.fittingType = "Elbow" 18 | params.dimensionsPixmap = "elbow-dimensions.png" 19 | params.explanationText = """

20 | To construct an elbow only these dimensions are used: 21 | BendingAngle, H, J, M, POD, and PThk. 22 | In Additinon, Dodo/Flamingo uses the Schedule dimension if it is present in the table. 23 | All other dimensions are used for inromation only. 24 |

""" 25 | params.settingsName = "elbow user input" 26 | params.keyColumnName = "PartNumber" 27 | super(MainDialog, self).__init__(params) 28 | 29 | def createNewPart(self, document, table, partName, outputType): 30 | builder = Elbow.ElbowFromTable(self.params.document, self.params.table) 31 | part = builder.create(partName, outputType) 32 | if outputType == Piping.OUTPUT_TYPE_DODO_OR_FLAMINGO: 33 | self.moveFlamingoPartToSelection(document, part) 34 | return part 35 | 36 | 37 | def GuiCheckTable(): 38 | return CreatePartGui.GuiCheckTable(Elbow.CSV_TABLE_PATH, Elbow.DIMENSIONS_USED) 39 | -------------------------------------------------------------------------------- /OsePiping/CrossGui.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Author: Ruslan Krenzler. 3 | # Date: 27 Januar 2018 4 | # Create a cross-fitting. 5 | 6 | import OsePiping.Cross as Cross 7 | import OsePiping.CreatePartGui as CreatePartGui 8 | import OsePiping.Piping as Piping 9 | 10 | 11 | class MainDialog(CreatePartGui.BaseDialog): 12 | def __init__(self, document, table): 13 | params = CreatePartGui.DialogParams() 14 | params.document = document 15 | params.table = table 16 | params.dialogTitle = "Create Cross" 17 | params.fittingType = "Cross" 18 | params.dimensionsPixmap = "cross-dimensions.png" 19 | params.explanationText = """

20 | To construct an cross only these dimensions are used: 21 | G, G1, H, H1, L, L1, M, M1, POD, POD1, PThk, Pthk1. 22 | In Additinon, Flamingo uses the Schedule dimension if it is present in the table. 23 | All other dimensions are used for inromation only. 24 |

""" 25 | params.settingsName = "cross user input" 26 | params.keyColumnName = "PartNumber" 27 | super(MainDialog, self).__init__(params) 28 | 29 | def createNewPart(self, document, table, partName, outputType): 30 | builder = Cross.CrossFromTable(self.params.document, self.params.table) 31 | part = builder.create(partName, outputType) 32 | if outputType == Piping.OUTPUT_TYPE_DODO_OR_FLAMINGO: 33 | self.moveFlamingoPartToSelection(document, part) 34 | return part 35 | 36 | 37 | def GuiCheckTable(): 38 | return CreatePartGui.GuiCheckTable(Cross.CSV_TABLE_PATH, Cross.DIMENSIONS_USED) 39 | -------------------------------------------------------------------------------- /OsePiping/CouplingGui.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Author: Ruslan Krenzler. 3 | # Date: 20 Januar December 2018 4 | # Create a coupling fitting using a gui. 5 | 6 | import OsePiping.Coupling as Coupling 7 | import OsePiping.CreatePartGui as CreatePartGui 8 | import OsePiping.Piping as Piping 9 | 10 | 11 | class MainDialog(CreatePartGui.BaseDialog): 12 | def __init__(self, document, table): 13 | params = CreatePartGui.DialogParams() 14 | params.document = document 15 | params.table = table 16 | params.dialogTitle = "Create Coupling" 17 | params.fittingType = "Coupling" 18 | params.dimensionsPixmap = "coupling-dimensions.png" 19 | params.explanationText = """

20 | To construct a coupling we use these dimensions, elbow only these dimensions are used: 21 | alpha, L, N, M, M1, POD, POD1, PThk, and PThk1. 22 | In Additinon, Flamingo uses the Schedule dimension if it is present in the table. 23 | All other dimensions are used for inromation only. 24 |

""" 25 | params.settingsName = "coupling user input" 26 | params.keyColumnName = "PartNumber" 27 | super(MainDialog, self).__init__(params) 28 | 29 | def createNewPart(self, document, table, partName, outputType): 30 | builder = Coupling.CouplingFromTable(self.params.document, self.params.table) 31 | part = builder.create(partName, outputType) 32 | if outputType == Piping.OUTPUT_TYPE_DODO_OR_FLAMINGO: 33 | self.moveFlamingoPartToSelection(document, part) 34 | return part 35 | 36 | 37 | def GuiCheckTable(): 38 | return CreatePartGui.GuiCheckTable(Coupling.CSV_TABLE_PATH, Coupling.DIMENSIONS_USED) 39 | -------------------------------------------------------------------------------- /OsePiping/PipingGui.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Author: Ruslan Krenzler. 3 | # Date: 20 February 2018 4 | # General classes for piping dialogs 5 | 6 | from PySide import QtCore 7 | 8 | 9 | class PartTableModel(QtCore.QAbstractTableModel): 10 | def __init__(self, headers, data, parent=None, *args): 11 | self.headers = headers 12 | self.table_data = data 13 | self.keyColumnName = None 14 | QtCore.QAbstractTableModel.__init__(self, parent, *args) 15 | 16 | def rowCount(self, parent): 17 | return len(self.table_data) 18 | 19 | def columnCount(self, parent): 20 | return len(self.headers) 21 | 22 | def data(self, index, role): 23 | if not index.isValid(): 24 | return None 25 | elif role != QtCore.Qt.DisplayRole: 26 | return None 27 | return self.table_data[index.row()][index.column()] 28 | 29 | def getPartKey(self, rowIndex): 30 | key_index = self.headers.index(self.keyColumnName) 31 | return self.table_data[rowIndex][key_index] 32 | 33 | def getPartRowIndex(self, key): 34 | """Return row index of the part with key *key*. 35 | 36 | The *key* is usually refers to the part number. 37 | :param key: Key of the part. 38 | :return: Index of the first row whose key is equal to key 39 | return -1 if no row find. 40 | """ 41 | key_index = self.headers.index(self.keyColumnName) 42 | for row_i in range(key_index, len(self.table_data)): 43 | if self.table_data[row_i][key_index] == key: 44 | return row_i 45 | return -1 46 | 47 | def headerData(self, col, orientation, role): 48 | if orientation == QtCore. Qt.Horizontal and role == QtCore.Qt.DisplayRole: 49 | return self.headers[col] 50 | return None 51 | -------------------------------------------------------------------------------- /COPYRIGHT.template: -------------------------------------------------------------------------------- 1 | #*************************************************************************** 2 | #* * 3 | #* This file is part of the FreeCAD_Workbench_Starter project. * 4 | #* * 5 | #* * 6 | #* Copyright (C) 2017 * 7 | #* Author * 8 | #* * 9 | #* This library is free software; you can redistribute it and/or * 10 | #* modify it under the terms of the GNU Lesser General Public * 11 | #* License as published by the Free Software Foundation; either * 12 | #* version 2 of the License, or (at your option) any later version. * 13 | #* * 14 | #* This library is distributed in the hope that it will be useful, * 15 | #* but WITHOUT ANY WARRANTY; without even the implied warranty of * 16 | #* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * 17 | #* Lesser General Public License for more details. * 18 | #* * 19 | #* You should have received a copy of the GNU Lesser General Public * 20 | #* License along with this library; if not, If not, see * 21 | #* . * 22 | #* * 23 | #* * 24 | #*************************************************************************** 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | 49 | # Translations 50 | *.mo 51 | *.pot 52 | 53 | # Django stuff: 54 | *.log 55 | local_settings.py 56 | 57 | # Flask stuff: 58 | instance/ 59 | .webassets-cache 60 | 61 | # Scrapy stuff: 62 | .scrapy 63 | 64 | # Sphinx documentation 65 | docs/_build/ 66 | 67 | # PyBuilder 68 | target/ 69 | 70 | # Jupyter Notebook 71 | .ipynb_checkpoints 72 | 73 | # pyenv 74 | .python-version 75 | 76 | # celery beat schedule file 77 | celerybeat-schedule 78 | 79 | # SageMath parsed files 80 | *.sage.py 81 | 82 | # dotenv 83 | .env 84 | 85 | # virtualenv 86 | .venv 87 | venv/ 88 | ENV/ 89 | 90 | # Spyder project settings 91 | .spyderproject 92 | .spyproject 93 | 94 | # Rope project settings 95 | .ropeproject 96 | 97 | # mkdocs documentation 98 | /site 99 | 100 | # mypy 101 | .mypy_cache/ 102 | 103 | # LibreCAD backup files. 104 | *.dxf~ 105 | # LyX backup files. 106 | *.lyx~ 107 | # gedit backup files 108 | \#* 109 | # Temporary file with GUI-python code, created using pyside-uic from .ui file. 110 | tmp.py 111 | # FreeCAD backup files. 112 | *.fcstd1 113 | 114 | -------------------------------------------------------------------------------- /tables/pipe.csv: -------------------------------------------------------------------------------- 1 | "PartNumber","PipeSize","PSize","Schedule","OD","ID","Thk" 2 | "NPS 1/8"" PVC SCH 40","1/8""","DN6",40,"0.405 in","0.249 in","0.068 in" 3 | "NPS 1/4"" PVC SCH 40","1/4""","DN8",40,"0.54 in","0.344 in","0.088 in" 4 | "NPS 3/8"" PVC SCH 40","3/8""","DN10",40,"0.675 in","0.473 in","0.091 in" 5 | "NPS 1/2"" PVC SCH 40","1/2""","DN15",40,"0.84 in","0.602 in","0.109 in" 6 | "NPS 3/4"" PVC SCH 40","3/4""","DN20",40,"1.05 in","0.804 in","0.113 in" 7 | "NPS 1"" PVC SCH 40","1""","DN25",40,"1.315 in","1.029 in","0.133 in" 8 | "NPS 1-1/4"" PVC SCH 40","1-1/4""","DN32",40,"1.66 in","1.36 in","0.14 in" 9 | "NPS 1-1/2"" PVC SCH 40","1-1/2""","DN40",40,"1.9 in","1.59 in","0.145 in" 10 | "NPS 2"" PVC SCH 40","2""","DN50",40,"2.375 in","2.047 in","0.154 in" 11 | "NPS 2-1/2"" PVC SCH 40","2-1/2""","DN65",40,"2.875 in","2.445 in","0.203 in" 12 | "NPS 3"" PVC SCH 40","3""","DN80",40,"3.5 in","3.042 in","0.216 in" 13 | "NPS 3-1/2"" PVC SCH 40","3-1/2""","DN90",40,"4.0 in","3.521 in","0.226 in" 14 | "NPS 4"" PVC SCH 40","4""","DN100",40,"4.5 in","3.998 in","0.237 in" 15 | "NPS 5"" PVC SCH 40","5""","DN125",40,"5.563 in","5.016 in","0.258 in" 16 | "NPS 6"" PVC SCH 40","6""","DN150",40,"6.625 in","6.031 in","0.28 in" 17 | "NPS 8"" PVC SCH 40","8""","DN200",40,"8.625 in","7.942 in","0.322 in" 18 | "NPS 10"" PVC SCH 40","10""","DN250",40,"10.75 in","9.976 in","0.365 in" 19 | "NPS 12"" PVC SCH 40","12""","DN300",40,"12.75 in","11.889 in","0.406 in" 20 | "NPS 1/8"" PVC SCH 80","1/8""","DN6",80,"0.405 in","0.195 in","0.095 in" 21 | "NPS 1/4"" PVC SCH 80","1/4""","DN8",80,"0.54 in","0.282 in","0.119 in" 22 | "NPS 3/8"" PVC SCH 80","3/8""","DN10",80,"0.675 in","0.403 in","0.126 in" 23 | "NPS 1/2"" PVC SCH 80","1/2""","DN15",80,"0.84 in","0.526 in","0.147 in" 24 | "NPS 3/4"" PVC SCH 80","3/4""","DN20",80,"1.05 in","0.722 in","0.154 in" 25 | "NPS 1"" PVC SCH 80","1""","DN25",80,"1.315 in","0.936 in","0.179 in" 26 | "NPS 1-1/4"" PVC SCH 80","1-1/4""","DN32",80,"1.66 in","1.255 in","0.191 in" 27 | "NPS 1-1/2"" PVC SCH 80","1-1/2""","DN40",80,"1.9 in","1.476 in","0.2 in" 28 | "NPS 2"" PVC SCH 80","2""","DN50",80,"2.375 in","1.913 in","0.218 in" 29 | "NPS 2-1/2"" PVC SCH 80","2-1/2""","DN65",80,"2.875 in","2.29 in","0.276 in" 30 | "NPS 3"" PVC SCH 80","3""","DN80",80,"3.5 in","2.864 in","0.3 in" 31 | "NPS 3-1/2"" PVC SCH 80","3-1/2""","DN90",80,"4.0 in","3.326 in","0.318 in" 32 | "NPS 4"" PVC SCH 80","4""","DN100",80,"4.5 in","3.786 in","0.337 in" 33 | "NPS 5"" PVC SCH 80","5""","DN125",80,"5.563 in","4.768 in","0.375 in" 34 | "NPS 6"" PVC SCH 80","6""","DN150",80,"6.625 in","5.709 in","0.432 in" 35 | "NPS 8"" PVC SCH 80","8""","DN200",80,"8.625 in","7.565 in","0.5 in" 36 | "NPS 10"" PVC SCH 80","10""","DN250",80,"10.75 in","9.493 in","0.593 in" 37 | "NPS 12"" PVC SCH 80","12""","DN300",80,"12.75 in","11.294 in","0.687 in" 38 | -------------------------------------------------------------------------------- /tables/sweep-elbow.csv: -------------------------------------------------------------------------------- 1 | PartNumber,PipeSize,BendAngle,PSize,Schedule,H,J,M,Approx. Wt.,POD,PThk,PID 2 | 406-005S,"1/2""",90 deg,DN15,40,1+23/32 in,27/32 in,1+3/16 in,.08 lb,0.840 in,0.109 in,0.602 in 3 | 406-007S,"3/4""",90 deg,DN20,40,2+1/32 in,1+1/32 in,1+13/32 in,.12 lb,1.050 in,0.113 in,0.804 in 4 | 406-010S,"1""",90 deg,DN25,40,2+7/16 in,1+5/16 in,1+23/32 in,.20 lb,1.315 in,0.133 in,1.029 in 5 | 406-012S,"1-1/4""",90 deg,DN32,40,2+13/16 in,1+9/16 in,2+3/32 in,.30 lb,1.660 in,0.140 in,1.360 in 6 | 406-015S,"1-1/2""",90 deg,DN40,40,3+5/32 in,1+3/4 in,2+11/32 in,.36 lb,1.900 in,0.145 in,1.590 in 7 | 406-020S,"2""",90 deg,DN50,40,3+11/16 in,2+9/32 in,2+3/4 in,.46 lb,2.375 in,0.154 in,2.047 in 8 | 406-025S,"2-1/2""",90 deg,DN65,40,4+31/32 in,3+3/16 in,3+11/32 in,.88 lb,2.875 in,0.203 in,2.445 in 9 | 406-030S,"3""",90 deg,DN80,40,4+15/16 in,3+1/32 in,4 in,1.25 lb,3.500 in,0.216 in,3.042 in 10 | 406-025LSF,"2-1/2""",90 deg,DN65,40,7+7/16 in,5+7/16 in,3+1/4 in,1.26 lb,2.875 in,0.203 in,2.445 in 11 | 406-030LSF,"3""",90 deg,DN80,40,8+5/8 in,6+3/8 in,3+15/16 in,1.87 lb,3.500 in,0.216 in,3.042 in 12 | 406-040LSF,"4""",90 deg,DN100,40,10+5/8 in,8+3/8 in,5 in,2.69 lb,4.500 in,0.237 in,3.998 in 13 | 406-060LSF,"6""",90 deg,DN150,40,16+1/4 in,13 in,7+3/16 in,6.92 lb,6.625 in,0.280 in,6.031 in 14 | 406-080LSF,"8""",90 deg,DN200,40,26+11/16 in,22+7/16 in,9+1/4 in,19.43 lb,8.625 in,0.322 in,7.942 in 15 | 806-005S,"1/2""",90 deg,DN15,80,1+23/32 in,27/32 in,1+3/16 in,.08 lb,0.840 in,0.147 in,0.526 in 16 | 806-007S,"3/4""",90 deg,DN20,80,2+1/32 in,1+1/32 in,1+13/32 in,.12 lb,1.050 in,0.154 in,0.722 in 17 | 806-010S,"1""",90 deg,DN25,80,2+7/16 in,1+5/16 in,1+23/32 in,.20 lb,1.315 in,0.179 in,0.936 in 18 | 806-012S,"1-1/4""",90 deg,DN32,80,2+13/16 in,1+9/16 in,2+3/32 in,.30 lb,1.660 in,0.191 in,1.255 in 19 | 806-015S,"1-1/2""",90 deg,DN40,80,3+1/8 in,1+3/4 in,2+11/32 in,.39 lb,1.900 in,0.200 in,1.476 in 20 | 806-020S,"2""",90 deg,DN50,80,3+13/16 in,2+5/16 in,2+7/8 in,.64 lb,2.375 in,0.218 in,1.913 in 21 | 806-005LSF,"1/2""",90 deg,DN15,80,2+3/4 in,1+7/8 in,1+1/8 in,.13 lb,0.840 in,0.147 in,0.526 in 22 | 806-007LSF,"3/4""",90 deg,DN20,80,3+7/16 in,2+7/16 in,1+7/16 in,.22 lb,1.050 in,0.154 in,0.722 in 23 | 806-010LSF,"1""",90 deg,DN25,80,4 in,2+7/8 in,1+11/16 in,.33 lb,1.315 in,0.179 in,0.936 in 24 | 806-012LSF,"1-1/4""",90 deg,DN32,80,4+7/8 in,3+5/8 in,2+1/16 in,.50 lb,1.660 in,0.191 in,1.255 in 25 | 806-015LSF,"1-1/2""",90 deg,DN40,80,5+1/2 in,4+1/8 in,2+5/16 in,.60 lb,1.900 in,0.200 in,1.476 in 26 | 806-020LSF,"2""",90 deg,DN50,80,6+5/16 in,4+3/4 in,2+13/16 in,.90 lb,2.375 in,0.218 in,1.913 in 27 | 806-025LSF,"2-1/2""",90 deg,DN65,80,7+3/4 in,5+3/4 in,3+7/16 in,1.65 lb,2.875 in,0.276 in,2.290 in 28 | 806-030LSF,"3""",90 deg,DN80,80,9 in,7 in,4+1/8 in,2.36 lb,3.500 in,0.300 in,2.864 in 29 | 806-040LSF,"4""",90 deg,DN100,80,11+1/4 in,9 in,5+3/16 in,3.96 lb,4.500 in,0.337 in,3.786 in 30 | 806-060LSF,"6""",90 deg,DN150,80,16+1/4 in,13 in,7+1/2 in,12.84 lb,6.625 in,0.432 in,5.709 in 31 | 806-080LSF,"8""",90 deg,DN200,80,27+3/4 in,23+1/2 in,9+5/8 in,31.58 lb,8.625 in,0.500 in,7.565 in 32 | -------------------------------------------------------------------------------- /OsePiping/PipeGui.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Author: Ruslan Krenzler. 3 | # Date: 04 February 2018 4 | # Create a pipe. 5 | 6 | from PySide import QtCore, QtGui 7 | import FreeCAD 8 | import OsePiping.Pipe as Pipe 9 | import OsePiping.Piping as Piping 10 | import OsePiping.CreatePartGui as CreatePartGui 11 | 12 | 13 | class MainDialog(CreatePartGui.BaseDialog): 14 | def __init__(self, document, table): 15 | params = CreatePartGui.DialogParams() 16 | params.document = document 17 | params.table = table 18 | params.dialogTitle = "Create Pipe" 19 | params.fittingType = "Pipe" 20 | params.dimensionsPixmap = "pipe-dimensions.png" 21 | params.explanationText = """

22 | To construct a part, only these dimensions are used: 23 | OD, Thk and the pipe height (length). 24 | Dodo/Flamingo also uses Schedule and DN if they are present in the table. All other dimensions are used for inromation. 25 |

""" 26 | params.keyColumnName = "PartNumber" 27 | super(MainDialog, self).__init__(params) 28 | 29 | def createLengthWidget(self, Dialog): 30 | self.widgetLengthInput = QtGui.QWidget(Dialog) 31 | self.widgetLengthInput.setMinimumSize(QtCore.QSize(0, 27)) 32 | self.widgetLengthInput.setObjectName("widgetLengthInput") 33 | self.labelHeight = QtGui.QLabel(self.widgetLengthInput) 34 | self.labelHeight.setGeometry(QtCore.QRect(0, 0, 121, 27)) 35 | sizePolicy = QtGui.QSizePolicy( 36 | QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Minimum) 37 | sizePolicy.setHorizontalStretch(0) 38 | sizePolicy.setVerticalStretch(0) 39 | sizePolicy.setHeightForWidth( 40 | self.labelHeight.sizePolicy().hasHeightForWidth()) 41 | self.labelHeight.setSizePolicy(sizePolicy) 42 | self.labelHeight.setMinimumSize(QtCore.QSize(0, 27)) 43 | self.labelHeight.setMaximumSize(QtCore.QSize(200, 16777215)) 44 | self.labelHeight.setObjectName("labelHeight") 45 | self.lineEditLength = QtGui.QLineEdit(self.widgetLengthInput) 46 | self.lineEditLength.setGeometry(QtCore.QRect(120, 0, 91, 27)) 47 | self.lineEditLength.setObjectName("lineEditLength") 48 | # Add text. 49 | self.labelHeight.setText(QtGui.QApplication.translate( 50 | "Dialog", "Height (Length):", None, CreatePartGui.UnicodeUTF8())) 51 | self.lineEditLength.setText(QtGui.QApplication.translate( 52 | "Dialog", "1 m", None, CreatePartGui.UnicodeUTF8())) 53 | return self.widgetLengthInput 54 | 55 | # Add customized UI elements after the type. That is the length ui 56 | def setupUi(self, Dialog): 57 | super(MainDialog, self).setupUi(Dialog) 58 | # Append UI for pipe length input after the output type block. 59 | after_index = self.verticalLayout.indexOf(self.outputTypeWidget) + 1 60 | widget = self.createLengthWidget(Dialog) 61 | self.verticalLayout.insertWidget(after_index, widget) 62 | 63 | def saveAdditionalData(self, settings): 64 | settings.setValue("lineEditLength", self.lineEditLength.text()) 65 | 66 | def restoreAdditionalInput(self, settings): 67 | text = settings.value("lineEditLength") 68 | if text is not None: 69 | self.lineEditLength.setText(text) 70 | 71 | def createNewPart(self, document, table, partName, outputType): 72 | length = FreeCAD.Units.parseQuantity(self.lineEditLength.text()) 73 | builder = Pipe.PipeFromTable(self.params.document, self.params.table) 74 | part = builder.create(partName, length, outputType) 75 | if outputType == Piping.OUTPUT_TYPE_DODO_OR_FLAMINGO: 76 | self.moveFlamingoPartToSelection(document, part) 77 | return part 78 | 79 | 80 | def GuiCheckTable(): 81 | return CreatePartGui.GuiCheckTable(Pipe.CSV_TABLE_PATH, Pipe.DIMENSIONS_USED) 82 | -------------------------------------------------------------------------------- /InitGui.py: -------------------------------------------------------------------------------- 1 | # *************************************************************************** 2 | # * * 3 | # * This file is part of the OSE project. * 4 | # * * 5 | # * * 6 | # * Copyright (C) 2017 * 7 | # * Ruslan Krenzler * 8 | # * Stephen Kaiser * 9 | # * * 10 | # * This library is free software; you can redistribute it and/or * 11 | # * modify it under the terms of the GNU Lesser General Public * 12 | # * License as published by the Free Software Foundation; either * 13 | # * version 2 of the License, or (at your option) any later version. * 14 | # * * 15 | # * This library is distributed in the hope that it will be useful, * 16 | # * but WITHOUT ANY WARRANTY; without even the implied warranty of * 17 | # * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * 18 | # * Lesser General Public License for more details. * 19 | # * * 20 | # * You should have received a copy of the GNU Lesser General Public * 21 | # * License along with this library; if not, If not, see * 22 | # * . * 23 | # * * 24 | # * * 25 | # *************************************************************************** 26 | 27 | 28 | class OsePipingWorkbench (Workbench): 29 | 30 | MenuText = "OSE Piping Workbench" 31 | ToolTip = "A piping workbench for Open Source Ecology part design" 32 | 33 | def __init__(self): 34 | # This is the only place, where I could initialize the workbach icon. 35 | import os 36 | import OsePipingBase 37 | self.__class__.Icon = os.path.join( 38 | OsePipingBase.ICON_PATH, "Workbench.svg") 39 | 40 | def Initialize(self): 41 | "This function is executed when FreeCAD starts" 42 | # Test try to load other modules 43 | import OsePipingBase 44 | # import here all the needed files that create your FreeCAD commands 45 | import OsePipingCommands 46 | self.partList = ["OsePiping_Pipe", "OsePiping_Coupling", "OsePiping_Bushing", "OsePiping_Elbow", "OsePiping_SweepElbow", 47 | "OsePiping_Tee", "OsePiping_Corner", "OsePiping_Cross"] # A list of command names created in the line above 48 | # creates a new toolbar with your commands 49 | self.appendToolbar("Ose Piping", self.partList) 50 | self.appendMenu("Command Menu", self.partList) # creates a new menu 51 | 52 | # Add commands to connect and rotate parts. 53 | self.modificationList = ["OsePiping_MoveAround", "OsePiping_MoveTo"] 54 | self.appendToolbar("Modification", self.modificationList) 55 | self.appendMenu("Modification", self.modificationList) # creates a new menu 56 | #OSE_PipingWorkbench.Icon = os.path.join(OSEBase.ICON_PATH,"Workbench.svg") 57 | 58 | # FreeCADGui.addIconPath(":/Resources/icons") 59 | # FreeCADGui.addLanguagePath(":/translations") 60 | # FreeCADGui.addPreferencePage(":/ui/preferences-ose.ui","OSE") 61 | # FreeCADGui.addPreferencePage(":/ui/preferences-osedefaults.ui","OSE") 62 | # self.appendMenu(["An existing Menu", "My submenu"], self.list) # appends a submenu to an existing menu 63 | 64 | def Activated(self): 65 | "This function is executed when the workbench is activated" 66 | return 67 | 68 | def Deactivated(self): 69 | "This function is executed when the workbench is deactivated" 70 | return 71 | 72 | def ContextMenu(self, recipient): 73 | "This is executed whenever the user right-clicks on screen" 74 | # "recipient" will be either "view" or "tree" 75 | # add commands to the context menu 76 | self.appendContextMenu("Piping commands", self.partList) 77 | 78 | def GetClassName(self): 79 | # this function is mandatory if this is a full python workbench 80 | return "Gui::PythonWorkbench" 81 | 82 | 83 | Gui.addWorkbench(OsePipingWorkbench()) 84 | -------------------------------------------------------------------------------- /OsePiping/Port.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Author: Ruslan Krenzler. 3 | # Date: 20 November 2018 4 | # Advanced Ports. Ports with normal vector and rotation references. 5 | 6 | import FreeCAD 7 | 8 | 9 | class AdvancedPort: 10 | def __init__(self, base=None, rotation=None): 11 | 12 | if base is None: 13 | base = FreeCAD.Vector(0, 0, 0) 14 | 15 | if rotation is None: 16 | rotation = FreeCAD.Rotation(0, 0, 0) 17 | 18 | self.placement = FreeCAD.Placement(base, rotation) 19 | 20 | def getNormal(self): 21 | return self.placement.Rotation.multVec(FreeCAD.Vector(1, 0, 0)) 22 | 23 | def getAngleReference(self): 24 | return self.placement.Rotation.multVec(FreeCAD.Vector(0, 1, 0)) 25 | 26 | def getPartRotation(self, other_placement, other_port): 27 | """Return a rotation matrix wihch will rotate this port to the other port. 28 | 29 | param other_placement: 30 | param other_port: other pOrt. 31 | 32 | """ 33 | # Note: Multiplication of rotation matrices is commutative! 34 | # Apply operations from right to left. 35 | # Rotat itself back, such that normal points to x axis and angle reference 36 | # r points to y axis. 37 | A_inv = self.placement.Rotation.inverted() 38 | # print("A_inv " + str(A_inv.toEuler())) 39 | # Rotate the port such that the x axis shows back, but the angle reference 40 | # coinsides with previous one. 41 | A_r = FreeCAD.Rotation(0, 180, 0) 42 | # print("A_r " + str(A_r.toEuler())) 43 | A = other_port.placement.Rotation 44 | # print("A " + str(A.toEuler())) 45 | R = other_placement.Rotation 46 | # print("R " + str(R.toEuler())) 47 | B = R.multiply(A.multiply(A_r.multiply(A_inv))) 48 | # print("B " + str(B.toEuler())) 49 | return B 50 | 51 | def getPartBase(self, other_placement, other_port): 52 | # Check find first the global bosition of the other portself. 53 | other_g_base = other_placement.Base + \ 54 | other_placement.Rotation.multVec(other_port.placement.Base) 55 | # Get new rotation. 56 | B = self.getPartRotation(other_placement, other_port) 57 | # Get new port position taking in account the adjusting rotation. 58 | adjusted_base = B.multVec(self.placement.Base) 59 | return other_g_base - adjusted_base 60 | 61 | def getPartPlacement(self, other_placement, other_port): 62 | """Return new part placment adjusted to the port of the other part.""" 63 | return FreeCAD.Placement(self.getPartBase(other_placement, other_port=other_port), 64 | self.getPartRotation(other_placement, other_port=other_port)) 65 | 66 | 67 | def testPorts(): 68 | # Port 0 in a tee. 69 | port1 = AdvancedPort(FreeCAD.Vector(0, 0, 2), FreeCAD.Rotation(0, -90, 0)) 70 | # Port 2 in a tee. 71 | port2 = AdvancedPort(FreeCAD.Vector(-2, 0, 0), 72 | FreeCAD.Rotation(180, 0, 180)) 73 | part_placement = FreeCAD.Placement() 74 | # part_placement = FreeCAD.Placement(FreeCAD.Vector(0, 1, 1), FreeCAD.Rotation(0, 0, 0)) 75 | 76 | print(port1.placement) 77 | print(port2.placement) 78 | 79 | print(port2.getPartPlacement(part_placement, port1)) 80 | 81 | 82 | def supportsAdvancedPort(part): 83 | """Check if the part contains advanced ports.""" 84 | if part is None: 85 | return False 86 | if hasattr(part, "PortRotationAngles"): 87 | return True # This is an OSE-fitting. 88 | if hasattr(part, "PType") and part.PType == u"Pipe": 89 | return True # This is a Flamingo pipe. 90 | return False # This is something else. 91 | 92 | 93 | def _guessPipeAdvancedPorts(part): 94 | """Return port advanced port of a vertical streight flamingo pipe.""" 95 | port_bottom = AdvancedPort(base=FreeCAD.Vector( 96 | part.Ports[0]), rotation=FreeCAD.Rotation(0, 90, 0)) 97 | port_top = AdvancedPort(base=FreeCAD.Vector( 98 | part.Ports[1]), rotation=FreeCAD.Rotation(0, -90, 0)) 99 | return [port_bottom, port_top] 100 | 101 | 102 | def _extractAdvancedPorts(part): 103 | """Extract advanced ports from a FeaturePython part.""" 104 | res = [] 105 | for i in range(0, len(part.Ports)): 106 | rotation_angles = part.PortRotationAngles[i] 107 | rotation = FreeCAD.Rotation( 108 | rotation_angles.x, rotation_angles.y, rotation_angles.z) 109 | port = AdvancedPort(base=FreeCAD.Vector( 110 | part.Ports[i]), rotation=rotation) 111 | res.append(port) 112 | return res 113 | 114 | 115 | def extractAdvancedPorts(part): 116 | if part.PType == u"Pipe": 117 | return _guessPipeAdvancedPorts(part) 118 | else: 119 | return _extractAdvancedPorts(part) 120 | 121 | 122 | def getNearestPort(part_placement, ports, point): 123 | d_so_far = float("inf") 124 | closest_port = None 125 | for port in ports: 126 | global_pos = part_placement.Base + \ 127 | part_placement.Rotation.multVec(port.placement.Base) 128 | d = global_pos.distanceToPoint(point) 129 | if d < d_so_far: 130 | d_so_far = d 131 | closest_port = port 132 | return closest_port 133 | 134 | 135 | def getNearestPortIndex(part_placement, ports, point): 136 | closest_port = getNearestPort(part_placement, ports, point) 137 | return ports.index(closest_port) 138 | 139 | # testPorts() 140 | -------------------------------------------------------------------------------- /ui/create-part.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Dialog 4 | 5 | 6 | 7 | 0 8 | 0 9 | 800 10 | 800 11 | 12 | 13 | 14 | Dialog Title 15 | 16 | 17 | 18 | 19 | 20 | true 21 | 22 | 23 | 24 | 75 25 | true 26 | 27 | 28 | 29 | false 30 | 31 | 32 | false 33 | 34 | 35 | <html><head/><body><p><span style=" color:#ef2929;">The Flamingo-Workbench is deprecated, please install Dodo instead.</span></p></body></html> 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 0 44 | 55 45 | 46 | 47 | 48 | Qt::LeftToRight 49 | 50 | 51 | 52 | 53 | 10 54 | 0 55 | 301 56 | 61 57 | 58 | 59 | 60 | Output type: 61 | 62 | 63 | 64 | 65 | 66 | true 67 | 68 | 69 | Solid 70 | 71 | 72 | true 73 | 74 | 75 | 76 | 77 | 78 | 79 | false 80 | 81 | 82 | Dodo/Flamingo 83 | 84 | 85 | false 86 | 87 | 88 | 89 | 90 | 91 | 92 | Parts 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | QAbstractItemView::SingleSelection 104 | 105 | 106 | QAbstractItemView::SelectRows 107 | 108 | 109 | 110 | 111 | 112 | 113 | <html><head/><body><p>Information about dimensions</p></body></html> 114 | 115 | 116 | Qt::AutoText 117 | 118 | 119 | true 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | ../Resources/images/coupling-dimensions.png 130 | 131 | 132 | Qt::AlignCenter 133 | 134 | 135 | 136 | 137 | 138 | 139 | Qt::Horizontal 140 | 141 | 142 | QDialogButtonBox::Cancel|QDialogButtonBox::Ok 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | buttonBox 152 | accepted() 153 | Dialog 154 | accept() 155 | 156 | 157 | 248 158 | 254 159 | 160 | 161 | 157 162 | 274 163 | 164 | 165 | 166 | 167 | buttonBox 168 | rejected() 169 | Dialog 170 | reject() 171 | 172 | 173 | 316 174 | 260 175 | 176 | 177 | 286 178 | 274 179 | 180 | 181 | 182 | 183 | 184 | -------------------------------------------------------------------------------- /tables/cross.csv: -------------------------------------------------------------------------------- 1 | "PartNumber","PipeSize","PSize","PipeSize1","PSize1","Schedule","G","G1","H","H1","L","L1","M","M1","Approx. Wt.","POD","PThk","PID","POD1","PThk1","PID1" 2 | "420-005","1/2""","DN15","1/2""","DN15",40,"17/32 in","17/32 in","1+9/32 in","1+9/32 in","2+17/32 in","2+17/32 in","1+3/32 in","1+3/32 in",".07 lb","0.840 in","0.109 in","0.602 in","0.840 in","0.109 in","0.602 in" 3 | "420-007","3/4""","DN20","3/4""","DN20",40,"9/16 in","9/16 in","1+9/16 in","1+9/16 in","3+1/8 in","3+1/8 in","1+5/16 in","1+5/16 in",".12 lb","1.050 in","0.113 in","0.804 in","1.050 in","0.113 in","0.804 in" 4 | "420-010","1""","DN25","1""","DN25",40,"13/16 in","13/16 in","1+15/16 in","1+15/16 in","3+7/8 in","3+7/8 in","1+5/8 in","1+5/8 in",".25 lb","1.315 in","0.133 in","1.029 in","1.315 in","0.133 in","1.029 in" 5 | "420-012","1-1/4""","DN32","1-1/4""","DN32",40,"1+3/32 in","1+3/32 in","2+11/32 in","2+11/32 in","4+23/32 in","4+23/32 in","2+1/16 in","2+1/16 in",".43 lb","1.660 in","0.140 in","1.360 in","1.660 in","0.140 in","1.360 in" 6 | "420-015","1-1/2""","DN40","1-1/2""","DN40",40,"1+1/4 in","1+1/4 in","2+19/32 in","2+19/32 in","5+5/32 in","5+5/32 in","2+5/16 in","2+5/16 in",".54 lb","1.900 in","0.145 in","1.590 in","1.900 in","0.145 in","1.590 in" 7 | "420-020","2""","DN50","2""","DN50",40,"1+17/32 in","1+17/32 in","2+29/32 in","2+29/32 in","5+27/32 in","5+27/32 in","2+13/16 in","2+13/16 in",".78 lb","2.375 in","0.154 in","2.047 in","2.375 in","0.154 in","2.047 in" 8 | "420-025","2-1/2""","DN65","2-1/2""","DN65",40,"1+21/32 in","1+21/32 in","3+21/32 in","3+21/32 in","7+11/32 in","7+11/32 in","3+3/8 in","3+3/8 in","1.61 lb","2.875 in","0.203 in","2.445 in","2.875 in","0.203 in","2.445 in" 9 | "420-030","3""","DN80","3""","DN80",40,"2+1/16 in","2+1/16 in","3+15/16 in","3+15/16 in","7+29/32 in","7+29/32 in","4+1/32 in","4+1/32 in","1.74 lb","3.500 in","0.216 in","3.042 in","3.500 in","0.216 in","3.042 in" 10 | "420-040","4""","DN100","4""","DN100",40,"2+1/2 in","2+1/2 in","4+1/2 in","4+1/2 in","9+1/32 in","9+1/32 in","5+1/32 in","5+1/32 in","3.22 lb","4.500 in","0.237 in","3.998 in","4.500 in","0.237 in","3.998 in" 11 | "420-050F","5""","DN125","5""","DN125",40,"6 in","6 in","9 in","9 in","18 in","18 in","6+1/16 in","6+1/16 in","13.68 lb","5.563 in","0.258 in","5.016 in","5.563 in","0.258 in","5.016 in" 12 | "420-060F","6""","DN150","6""","DN150",40,"6+3/16 in","6+3/16 in","9+7/16 in","9+7/16 in","18+7/8 in","18+7/8 in","7+3/16 in","7+3/16 in","12.36 lb","6.625 in","0.280 in","6.031 in","6.625 in","0.280 in","6.031 in" 13 | "420-080F","8""","DN200","8""","DN200",40,"8+9/16 in","8+9/16 in","12+13/16 in","12+13/16 in","25+5/8 in","25+5/8 in","9+1/4 in","9+1/4 in","26.10 lb","8.625 in","0.322 in","7.942 in","8.625 in","0.322 in","7.942 in" 14 | "420-100F","10""","DN250","10""","DN250",40,"9+7/8 in","9+3/8 in","15+1/8 in","14+5/8 in","30+1/4 in","29+1/4 in","11+1/2 in","11+1/2 in","43.81 lb","10.750 in","0.365 in","9.976 in","10.750 in","0.365 in","9.976 in" 15 | "420-120F","12""","DN300","12""","DN300",40,"10+3/4 in","10+3/16 in","17 in","16+7/16 in","34 in","32+7/8 in","13+9/16 in","13+9/16 in","66.22 lb","12.750 in","0.406 in","11.889 in","12.750 in","0.406 in","11.889 in" 16 | "820-002","1/4""","DN8","1/4""","DN8",80,"13/32 in","13/32 in","1+1/32 in","1+1/32 in","2+1/16 in","2+1/16 in","27/32 in","27/32 in",".05 lb","0.540 in","0.119 in","0.282 in","0.540 in","0.119 in","0.282 in" 17 | "820-005","1/2""","DN15","1/2""","DN15",80,"9/16 in","9/16 in","1+15/32 in","1+15/32 in","2+15/16 in","2+15/16 in","1+3/16 in","1+3/16 in",".13 lb","0.840 in","0.147 in","0.526 in","0.840 in","0.147 in","0.526 in" 18 | "820-007","3/4""","DN20","3/4""","DN20",80,"5/8 in","5/8 in","1+5/8 in","1+5/8 in","3+9/32 in","3+9/32 in","1+13/32 in","1+13/32 in",".22 lb","1.050 in","0.154 in","0.722 in","1.050 in","0.154 in","0.722 in" 19 | "820-010","1""","DN25","1""","DN25",80,"31/32 in","31/32 in","2+3/32 in","2+3/32 in","4+7/32 in","4+7/32 in","1+23/32 in","1+23/32 in",".39 lb","1.315 in","0.179 in","0.936 in","1.315 in","0.179 in","0.936 in" 20 | "820-012","1-1/4""","DN32","1-1/4""","DN32",80,"1+3/32 in","1+3/32 in","2+3/8 in","2+3/8 in","4+3/4 in","4+3/4 in","2+1/8 in","2+1/8 in",".63 lb","1.660 in","0.191 in","1.255 in","1.660 in","0.191 in","1.255 in" 21 | "820-015","1-1/2""","DN40","1-1/2""","DN40",80,"1+1/4 in","1+1/4 in","2+5/8 in","2+5/8 in","5+1/4 in","5+1/4 in","2+3/8 in","2+3/8 in",".80 lb","1.900 in","0.200 in","1.476 in","1.900 in","0.200 in","1.476 in" 22 | "820-020","2""","DN50","2""","DN50",80,"1+1/2 in","1+1/2 in","3 in","3 in","6 in","6 in","3 in","3 in","1.34 lb","2.375 in","0.218 in","1.913 in","2.375 in","0.218 in","1.913 in" 23 | "820-025","2-1/2""","DN65","2-1/2""","DN65",80,"1+11/16 in","1+11/16 in","3+15/32 in","3+15/32 in","6+29/32 in","6+29/32 in","3+17/32 in","3+17/32 in","2.02 lb","2.875 in","0.276 in","2.290 in","2.875 in","0.276 in","2.290 in" 24 | "820-030","3""","DN80","3""","DN80",80,"2+3/32 in","2+3/32 in","3+31/32 in","3+31/32 in","7+15/16 in","7+15/16 in","4+11/32 in","4+11/32 in","3.30 lb","3.500 in","0.300 in","2.864 in","3.500 in","0.300 in","2.864 in" 25 | "820-040","4""","DN100","4""","DN100",80,"2+5/8 in","2+5/8 in","4+7/8 in","4+7/8 in","9+3/4 in","9+3/4 in","5+11/32 in","5+11/32 in","5.31 lb","4.500 in","0.337 in","3.786 in","4.500 in","0.337 in","3.786 in" 26 | "820-050F","5""","DN125","5""","DN125",80,"5+1/2 in","5+1/2 in","8+1/2 in","8+1/2 in","17 in","17 in","6+5/16 in","6+5/16 in","14.69 lb","5.563 in","0.375 in","4.768 in","5.563 in","0.375 in","4.768 in" 27 | "820-060F","6""","DN150","6""","DN150",80,"6+1/4 in","6+1/4 in","9+1/2 in","9+1/2 in","19 in","19 in","7+1/2 in","7+1/2 in","16.19 lb","6.625 in","0.432 in","5.709 in","6.625 in","0.432 in","5.709 in" 28 | "820-080F","8""","DN200","8""","DN200",80,"7+5/8 in","7+5/8 in","11+7/8 in","11+7/8 in","23+3/4 in","23+3/4 in","9+5/8 in","9+5/8 in","28.22 lb","8.625 in","0.500 in","7.565 in","8.625 in","0.500 in","7.565 in" 29 | "820-100F","10""","DN250","10""","DN250",80,"9 in","9 in","14+1/4 in","14+1/4 in","28+1/2 in","28+1/2 in","11+15/16 in","11+15/16 in","49.78 lb","10.750 in","0.593 in","9.493 in","10.750 in","0.593 in","9.493 in" 30 | "820-120F","12""","DN300","12""","DN300",80,"11+1/4 in","11+1/4 in","17+1/2 in","17+1/2 in","35 in","35 in","14+1/8 in","14+1/8 in","80.83 lb","12.750 in","0.687 in","11.294 in","12.750 in","0.687 in","11.294 in" -------------------------------------------------------------------------------- /Resources/icons/Workbench.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 21 | 23 | 34 | 36 | 40 | 44 | 45 | 56 | 63 | 74 | 77 | 81 | 85 | 86 | 87 | 107 | 114 | 115 | 117 | 118 | 120 | image/svg+xml 121 | 123 | 124 | 125 | [wmayer] 126 | 127 | 128 | Part_Cylinder 129 | 2011-10-10 130 | http://www.freecadweb.org/wiki/index.php?title=Artwork 131 | 132 | 133 | FreeCAD 134 | 135 | 136 | FreeCAD/src/Mod/Part/Gui/Resources/icons/Part_Cylinder.svg 137 | 138 | 139 | FreeCAD LGPL2+ 140 | 141 | 142 | https://www.gnu.org/copyleft/lesser.html 143 | 144 | 145 | [agryson] Alexander Gryson 146 | 147 | 148 | 149 | 150 | 151 | 155 | 161 | 167 | 174 | 175 | 176 | -------------------------------------------------------------------------------- /Resources/icons/OSE_Piping_workbench_icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 21 | 23 | 34 | 36 | 40 | 44 | 45 | 56 | 63 | 74 | 77 | 81 | 85 | 86 | 87 | 107 | 114 | 115 | 117 | 118 | 120 | image/svg+xml 121 | 123 | 124 | 125 | [wmayer] 126 | 127 | 128 | Part_Cylinder 129 | 2011-10-10 130 | http://www.freecadweb.org/wiki/index.php?title=Artwork 131 | 132 | 133 | FreeCAD 134 | 135 | 136 | FreeCAD/src/Mod/Part/Gui/Resources/icons/Part_Cylinder.svg 137 | 138 | 139 | FreeCAD LGPL2+ 140 | 141 | 142 | https://www.gnu.org/copyleft/lesser.html 143 | 144 | 145 | [agryson] Alexander Gryson 146 | 147 | 148 | 149 | 150 | 151 | 155 | 161 | 167 | 174 | 175 | 176 | -------------------------------------------------------------------------------- /OsePiping/Piping.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Author: Ruslan Krenzler. 3 | # Date: 17 February 2018 4 | # General classes for pipe and fittng parts. 5 | 6 | import csv 7 | import Part 8 | 9 | 10 | class Error(Exception): 11 | """Base class for exceptions in this module.""" 12 | 13 | def __init__(self, message): 14 | super(Error, self).__init__(message) 15 | 16 | 17 | class UnplausibleDimensions(Error): 18 | """Exception raised when dimensions are unplausible. 19 | 20 | For example if outer diameter is larger than the iner one. 21 | Attributes: 22 | message -- explanation of the error. 23 | 24 | """ 25 | 26 | def __init__(self, message): 27 | super(UnplausibleDimensions, self).__init__(message) 28 | 29 | 30 | def nestedObjects(parent): 31 | """Return a list of a nested object contained in the parent parts. 32 | 33 | Children are added before the parents. 34 | """ 35 | res = [] 36 | if parent.OutList == []: 37 | res.append(parent) 38 | else: 39 | # Append children first. 40 | for o in parent.OutList: 41 | res += nestedObjects(o) 42 | res.append(parent) 43 | return res 44 | 45 | 46 | def removePartWithChildren(document, part): 47 | parts = nestedObjects(part) 48 | # Document.removeObjects can remove multple objects, when we use 49 | # parts directly. To prevent exceptions with deleted objects, 50 | # use the name list instead. 51 | names_to_remove = [] 52 | for part in parts: 53 | if part.Name not in names_to_remove: 54 | names_to_remove.append(part.Name) 55 | for name in names_to_remove: 56 | # print("Deleting temporary objects %s." % name) 57 | document.removeObject(name) 58 | 59 | 60 | def toSolid(document, part, name): 61 | """Convert object to a solid. 62 | 63 | These are commands, which FreeCAD runs when a user converts a part to a solid. 64 | """ 65 | s = part.Shape.Faces 66 | s = Part.Solid(Part.Shell(s)) 67 | o = document.addObject("Part::Feature", name) 68 | o.Label = name 69 | o.Shape = s 70 | return o 71 | 72 | 73 | class CsvError(Error): 74 | """Base class for exceptions in this module.""" 75 | 76 | def __init__(self, message): 77 | super(Error, self).__init__(message) 78 | 79 | 80 | class CsvTable: 81 | """Read pipe dimensions from a csv file. 82 | 83 | One part of the column must be unique and contains a unique key. 84 | 85 | Store the data as a list of rows. Each row is a list of values. 86 | """ 87 | 88 | def __init__(self, mandatoryDims=None, keyColumnName="PartNumber"): 89 | """Initialize Class. 90 | 91 | @param mandatoryDims: list of column names which must be presented in the CSV files apart 92 | the "keyColumnName" column 93 | """ 94 | self.headers = [] 95 | self.data = [] 96 | self.hasValidData = False 97 | if mandatoryDims is None: 98 | mandatoryDims = [] 99 | self.mandatoryDims = mandatoryDims 100 | self._keyColumnName = keyColumnName 101 | self._keyColumnIndex = None 102 | 103 | def keyColumnName(self): 104 | return self._keyColumnName 105 | 106 | def load(self, filename): 107 | """Load data from a CSV file.""" 108 | self.hasValidData = False 109 | with open(filename, "r") as csvfile: 110 | csv_reader = csv.reader(csvfile, delimiter=',', quotechar='"') 111 | self.headers = next(csv_reader) 112 | # Fill the talble 113 | self.data = [] 114 | keys = [] 115 | self._keyColumnIndex = self.headers.index(self._keyColumnName) 116 | for row in csv_reader: 117 | # Check if the keys is unique 118 | key = row[self._keyColumnIndex] 119 | if key in keys: 120 | msg = 'Error: Not unique key "%s" in column %s found in %s' % ( 121 | key, self._keyColumnName, filename) 122 | raise CsvError(msg) 123 | else: 124 | keys.append(key) 125 | self.data.append(row) 126 | csvfile.close() # Should I close this file explicitely? 127 | self.hasValidData = self.hasNecessaryColumns() 128 | 129 | def hasNecessaryColumns(self): 130 | """Check if the data contains all the columns required to create a part.""" 131 | return all(h in self.headers for h in (self.mandatoryDims + [self._keyColumnName])) 132 | 133 | def findPart(self, key): 134 | """Return first row with with key (part name) as a dictionary.""" 135 | # Search for the first appearance of the name in this column. 136 | for row in self.data: 137 | if row[self._keyColumnIndex] == key: 138 | # Convert row to dicionary. 139 | return dict(zip(self.headers, row)) 140 | return None 141 | 142 | def getPartKey(self, index): 143 | """Return part key of a row with the index *index*.""" 144 | return self.data[index][self._keyColumnIndex] 145 | 146 | 147 | OUTPUT_TYPE_PARTS = 1 148 | OUTPUT_TYPE_SOLID = 2 149 | OUTPUT_TYPE_DODO_OR_FLAMINGO = 3 150 | 151 | 152 | def GetPressureRatingString(row): 153 | """Extract presssure rating string from a row of a fitting table. 154 | 155 | The Pressure rating has a form "SCH-[Schedule]". For example "SCH-40". 156 | 157 | :param row: a dictionary which contains non empty elements "Schedule" or "SCH". 158 | :return: String like "SCH-40" or "SCH-80". 159 | :return "": if there is no schedule data in the row. 160 | """ 161 | if row.get("Schedule") is not None and len(row["Schedule"]) > 0: 162 | return "SCH-%s" % row["Schedule"] 163 | if row.get("SCH") is not None and len(row["SCH"]) > 0: 164 | return "SCH-%s" % row["SCH"] 165 | else: 166 | return "" # Nothing found 167 | 168 | 169 | def GetDnString(row): 170 | """Extract DN (diamètre nominal, nominal pipe size) string from a row of a fitting table. 171 | 172 | The Pressure rating has a form "DN[DN-Value]". For example "DN25". 173 | 174 | :param row: a dictionary which contains non empty elements "ND". 175 | :return: String like "DN10" or "DN25". 176 | :return "": if there is no DN data in the row. 177 | """ 178 | if row.get("DN") is not None and len(row["DN"]) > 0: 179 | return "DN%s" % row["DN"] 180 | else: 181 | return "" # Nothing found 182 | 183 | 184 | def HasDodoSupport(): 185 | import pkgutil 186 | mod = pkgutil.find_loader("pFeatures") 187 | return mod is not None 188 | 189 | 190 | def HasFlamingoSupport(): 191 | import pkgutil 192 | mod = pkgutil.find_loader("pipeFeatures") 193 | return mod is not None 194 | -------------------------------------------------------------------------------- /ui/create-pipe.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Dialog 4 | 5 | 6 | 7 | 0 8 | 0 9 | 836 10 | 633 11 | 12 | 13 | 14 | Create pipe 15 | 16 | 17 | 18 | 19 | 20 | 21 | 0 22 | 55 23 | 24 | 25 | 26 | Qt::LeftToRight 27 | 28 | 29 | 30 | 31 | 10 32 | 0 33 | 351 34 | 61 35 | 36 | 37 | 38 | Output type: 39 | 40 | 41 | 42 | 43 | 44 | true 45 | 46 | 47 | Solid 48 | 49 | 50 | true 51 | 52 | 53 | 54 | 55 | 56 | 57 | Parts 58 | 59 | 60 | 61 | 62 | 63 | 64 | false 65 | 66 | 67 | Flamingo (depricated) 68 | 69 | 70 | false 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 0 83 | 27 84 | 85 | 86 | 87 | 88 | 89 | 0 90 | 0 91 | 121 92 | 27 93 | 94 | 95 | 96 | 97 | 0 98 | 0 99 | 100 | 101 | 102 | 103 | 0 104 | 27 105 | 106 | 107 | 108 | 109 | 200 110 | 16777215 111 | 112 | 113 | 114 | Height (Length): 115 | 116 | 117 | 118 | 119 | 120 | 120 121 | 0 122 | 91 123 | 27 124 | 125 | 126 | 127 | 1 m 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 19 136 | 137 | 138 | 139 | 140 | QAbstractItemView::SingleSelection 141 | 142 | 143 | QAbstractItemView::SelectRows 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | ../Resources/images/pipe-dimensions.png 154 | 155 | 156 | Qt::AlignCenter 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | <html><head/><body><p>To construct a part, only these dimensions are used: OD, Thk and the pipe height (length). Flamingo also uses Schedule and DN if they are present in the table. All other dimensions are used for inromation.</p></body></html> 166 | 167 | 168 | Qt::AutoText 169 | 170 | 171 | true 172 | 173 | 174 | 175 | 176 | 177 | 178 | Qt::Horizontal 179 | 180 | 181 | QDialogButtonBox::Cancel|QDialogButtonBox::Ok 182 | 183 | 184 | 185 | 186 | 187 | 188 | radioButtonSolid 189 | radioButtonFlamingo 190 | lineEditLength 191 | buttonBox 192 | 193 | 194 | 195 | 196 | buttonBox 197 | accepted() 198 | Dialog 199 | accept() 200 | 201 | 202 | 248 203 | 254 204 | 205 | 206 | 157 207 | 274 208 | 209 | 210 | 211 | 212 | buttonBox 213 | rejected() 214 | Dialog 215 | reject() 216 | 217 | 218 | 316 219 | 260 220 | 221 | 222 | 286 223 | 274 224 | 225 | 226 | 227 | 228 | 229 | -------------------------------------------------------------------------------- /OsePiping/Pipe.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Author: Ruslan Krenzler. 3 | # Date: 04 February 2018 4 | # Create a pipe. 5 | 6 | import os.path 7 | import FreeCAD 8 | import OsePipingBase 9 | import OsePiping.Piping as Piping 10 | 11 | 12 | parseQuantity = FreeCAD.Units.parseQuantity 13 | 14 | # This is the path to the dimensions table. 15 | CSV_TABLE_PATH = os.path.join(OsePipingBase.TABLE_PATH, 'pipe.csv') 16 | # It must contain unique values in the column "Name" and also, dimensions listened below. 17 | DIMENSIONS_USED = ["OD", "Thk"] 18 | 19 | 20 | # The value RELATIVE_EPSILON is used to slightly change the size of a subtracted part 21 | # to prevent problems with boolean operations. 22 | # This value does not change the appearance of part and can be large. 23 | # If the original value is L then we often use the value L*(1+RELATIVE_EPSILON) instead. 24 | # The relative deviation is then (L*(1+RELATIVE_EPSILON)-L)/L = RELATIVE_EPSILON. 25 | # That is why the constant has "relative" in its name. 26 | # On my version of freecad 0.16 The macro works even with RELATIVE_EPSILON = 0.0. 27 | # Maybe there is no more problems with boolean operations. 28 | RELATIVE_EPSILON = 0.1 29 | 30 | 31 | class Dimensions: 32 | def __init__(self): 33 | """Inititialize with test dimensions.""" 34 | self.OD = parseQuantity("3 cm") 35 | self.Thk = parseQuantity("0.5 cm") 36 | self.H = parseQuantity("1 m") 37 | 38 | def isValid(self): 39 | errorMsg = "" 40 | if not (self.OD > 0): 41 | errorMsg = "OD (inner diameter) of the pipe must be positive. It is {} instead".format( 42 | self.OD) 43 | elif not (self.Thk <= self.OD / 2.0): 44 | errorMsg = "Pipe thickness Thk {} is too large: larger than OD/2 {}.".format( 45 | self.Thk, self.OD / 2.0) 46 | elif not (self.H > 0): 47 | errorMsg = "Height H={} must be positive".format(self.H) 48 | 49 | return (len(errorMsg) == 0, errorMsg) 50 | 51 | def ID(self): 52 | return self.OD - self.Thk * 2.0 53 | 54 | 55 | class Pipe: 56 | def __init__(self, document): 57 | self.document = document 58 | self.dims = Dimensions() 59 | 60 | def checkDimensions(self): 61 | valid, msg = self.dims.isValid() 62 | if not valid: 63 | raise Piping.UnplausibleDimensions(msg) 64 | 65 | def create(self, convertToSolid): 66 | """Create a pipe which is a differences of two cilinders: outer cylinder - inner cylinder. 67 | 68 | :param convertToSolid: if true, the resulting part will be solid. 69 | if false, the resulting part will be a cut. 70 | :return resulting part. 71 | """ 72 | self.checkDimensions() 73 | # Create outer cylinder. 74 | outer_cylinder = self.document.addObject( 75 | "Part::Cylinder", "OuterCylinder") 76 | outer_cylinder.Radius = self.OD / 2 77 | outer_cylinder.Height = self.H 78 | 79 | # Create inner cylinder. It is a little bit longer than the outer cylider in both ends. 80 | # This should prevent numerical problems when calculating difference 81 | # between the outer and innter cylinder. 82 | inner_cylinder = self.document.addObject( 83 | "Part::Cylinder", "InnerCylinder") 84 | inner_cylinder.Radius = self.OD / 2 - self.Thk 85 | inner_cylinder.Height = self.H * (1 + 2 * RELATIVE_EPSILON) 86 | inner_cylinder.Placement.Base = FreeCAD.Vector( 87 | 0, 0, -self.H * RELATIVE_EPSILON) 88 | pipe = self.document.addObject("Part::Cut", "Pipe") 89 | pipe.Base = outer_cylinder 90 | pipe.Tool = inner_cylinder 91 | 92 | if convertToSolid: 93 | # Before making a solid, recompute documents. Otherwise there will be 94 | # s = Part.Solid(Part.Shell(s)) 95 | # : Shape is null 96 | # exception. 97 | self.document.recompute() 98 | # Now convert all parts to solid, and remove intermediate data. 99 | solid = Piping.toSolid(self.document, pipe, "pipe (solid)") 100 | Piping.removePartWithChildren(self.document, pipe) 101 | return solid 102 | return pipe 103 | 104 | 105 | def getDFPipe(obj, DN, OD, thk, H): 106 | """Get pipe features from Dodo or from Flamingo workbench. """ 107 | try: 108 | import pFeatures 109 | FreeCAD.Console.PrintMessage("Creating Dodo pipe.") 110 | return pFeatures.Pipe(obj, DN=DN, OD=OD, thk=thk, H=H) 111 | except ModuleNotFoundError: 112 | FreeCAD.Console.PrintMessage("Dodo workbench is not found. I will use Flamingo instead.") 113 | import pipeFeatures 114 | return pipeFeatures.Pipe(obj, DN=DN, OD=OD, thk=thk, H=H) 115 | 116 | 117 | class PipeFromTable: 118 | """Create a part with dimensions from a CSV table.""" 119 | 120 | def __init__(self, document, table): 121 | self.document = document 122 | self.table = table 123 | 124 | def create(self, partName, length, outputType): 125 | row = self.table.findPart(partName) 126 | if row is None: 127 | print("Part not found") 128 | return 129 | if outputType == Piping.OUTPUT_TYPE_PARTS or outputType == Piping.OUTPUT_TYPE_SOLID: 130 | pipe = Pipe(self.document) 131 | pipe.OD = parseQuantity(row["OD"]) 132 | pipe.Thk = parseQuantity(row["Thk"]) 133 | pipe.H = length 134 | part = pipe.create(outputType == Piping.OUTPUT_TYPE_SOLID) 135 | return part 136 | elif outputType == Piping.OUTPUT_TYPE_DODO_OR_FLAMINGO: 137 | # See Code in pipeCmd.makePipe in the Flamingo workbench. 138 | feature = self.document.addObject( 139 | "Part::FeaturePython", "OSE-Pipe") 140 | DN = Piping.GetDnString(row) 141 | OD = parseQuantity(row["OD"]) 142 | Thk = parseQuantity(row["Thk"]) 143 | part = getDFPipe(feature, DN=DN, OD=OD, thk=Thk, H=length) 144 | feature.PRating = Piping.GetPressureRatingString(row) 145 | # Currently I do not know how to interprite table data as a profile. 146 | feature.Profile = "" 147 | if "PSize" in row.keys(): 148 | feature.PSize = row["PSize"] 149 | # Workaround. Add ports before return. Otherwise the positioning is not working. 150 | feature.Ports = [FreeCAD.Vector(0, 0, 0), FreeCAD.Vector(0, 0, length)] 151 | feature.ViewObject.Proxy = 0 152 | return part 153 | 154 | 155 | # Test macros. 156 | def TestPipe(): 157 | document = FreeCAD.activeDocument() 158 | pipe = Pipe(document) 159 | pipe.create(False) 160 | document.recompute() 161 | 162 | 163 | def TestTable(): 164 | document = FreeCAD.activeDocument() 165 | table = Piping.CsvTable(DIMENSIONS_USED) 166 | table.load(CSV_TABLE_PATH) 167 | pipe = PipeFromTable(document, table) 168 | for i in range(0, len(table.data)): 169 | print("Selecting row %d" % i) 170 | partName = table.getPartName(i) 171 | print("Creating part %s" % partName) 172 | pipe.create(partName, parseQuantity("1m"), False) 173 | document.recompute() 174 | -------------------------------------------------------------------------------- /OsePiping/FlCorner.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Authors: Ruslan Krenzler, oddtopus. 3 | # Date: 25 March 2018 4 | # Create an outer corner using Flamingo workbench. 5 | 6 | import FreeCAD 7 | import Part 8 | # Parent class from Dodo or Flamingo. 9 | try: 10 | from pFeatures import pypeType 11 | except ModuleNotFoundError: 12 | from pipeFeatures import pypeType 13 | import OsePiping.Corner as CornerMod 14 | 15 | 16 | class Corner(pypeType): 17 | def __init__(self, obj, PSize="", dims=CornerMod.Dimensions()): 18 | """Create an outer corner with the center at (0,0,0) and elbows along x, y and z axis. """ 19 | # Run parent __init__ and define common attributes 20 | super(Corner, self).__init__(obj) 21 | obj.PType = "OSE_Corner" 22 | obj.PRating = "" 23 | obj.PSize = PSize 24 | # Define specific attributes and set their values. 25 | obj.addProperty("App::PropertyLength", "G", "Corner", 26 | "Distnace from the center to begin of innerpart of the socket").G = dims.G 27 | obj.addProperty("App::PropertyLength", "H", "Corner", 28 | "Distance between the center and a corner end").H = dims.H 29 | obj.addProperty("App::PropertyLength", "M", "Corner", 30 | "Outside diameter of the corner.").M = dims.M 31 | obj.addProperty("App::PropertyLength", "POD", "Corner", 32 | "Pipe outer diameter.").POD = dims.POD 33 | obj.addProperty("App::PropertyLength", "PThk", "Corner", 34 | "Thickness of the pipe.").PThk = dims.PThk 35 | 36 | # New Dodo-pypeType already contains Ports, use them. 37 | # But if pypeType does does not have Ports, add them. 38 | if "App::PropertyVectorList" not in obj.supportedProperties(): 39 | obj.addProperty("App::PropertyVectorList", "Ports", "Corner", "Ports relative positions.") 40 | obj.Ports = self.getPorts(obj) 41 | 42 | obj.addProperty("App::PropertyVectorList", "PortRotationAngles", "Corner", 43 | "Ports rotation angles.").PortRotationAngles = self.getPortRotationAngles(obj) 44 | obj.addProperty("App::PropertyString", "PartNumber", 45 | "Corner", "Part number").PartNumber = "" 46 | # Make Ports read only. 47 | obj.setEditorMode("Ports", 1) 48 | obj.setEditorMode("PortRotationAngles", 1) 49 | 50 | def onChanged(self, obj, prop): 51 | # if you aim to do something when an attribute is changed 52 | # place the code here: 53 | # e.g. -> change PSize according the new alpha, PID and POD 54 | 55 | dim_properties = ["G"] # Dimensions which influence port coordinates. 56 | if prop in dim_properties: 57 | # This function is called within __init__ too. Thus we need to wait until 58 | # we have all the required attributes. 59 | if set(CornerMod.DIMENSIONS_USED).issubset(obj.PropertiesList): 60 | obj.Ports = self.getPorts(obj) 61 | # Wait until PortRotationAngles are defined. 62 | if hasattr(obj, "PortRotationAngles"): 63 | obj.PortRotationAngles = self.getPortRotationAngles(obj) 64 | 65 | @classmethod 66 | def extractDimensions(cls, obj): 67 | dims = CornerMod.Dimensions() 68 | dims.G = obj.G 69 | dims.H = obj.H 70 | dims.M = obj.M 71 | dims.POD = obj.POD 72 | dims.PThk = obj.PThk 73 | return dims 74 | 75 | @classmethod 76 | def createPrimitiveCorner(cls, L, D): 77 | """Create corner consisting of two cylinder along x-,y- and y axis and a ball in the center.""" 78 | x_cylinder = Part.makeCylinder( 79 | D / 2, L, FreeCAD.Vector(0, 0, 0), FreeCAD.Vector(1, 0, 0)) 80 | y_cylinder = Part.makeCylinder( 81 | D / 2, L, FreeCAD.Vector(0, 0, 0), FreeCAD.Vector(0, 1, 0)) 82 | z_cylinder = Part.makeCylinder( 83 | D / 2, L, FreeCAD.Vector(0, 0, 0), FreeCAD.Vector(0, 0, 1)) 84 | sphere = Part.makeSphere(D / 2) 85 | return sphere.fuse([x_cylinder, y_cylinder, z_cylinder]) 86 | 87 | @classmethod 88 | def createOuterPart(cls, obj): 89 | dims = cls.extractDimensions(obj) 90 | return cls.createPrimitiveCorner(dims.H, dims.M) 91 | 92 | @classmethod 93 | def createInnerPart(cls, obj): 94 | dims = cls.extractDimensions(obj) 95 | inner = cls.createPrimitiveCorner(dims.H, dims.PID()) 96 | inner = Corner.addSockets(dims.POD, dims.H, dims.G, inner) 97 | return inner 98 | 99 | @classmethod 100 | def addSockets(cls, D, H, G, shape): 101 | """Add socket cylinders with diamater D to the ends of the corner shape.""" 102 | x_socket = Part.makeCylinder( 103 | D / 2, H - G, FreeCAD.Vector(G, 0, 0), FreeCAD.Vector(1, 0, 0)) 104 | y_socket = Part.makeCylinder( 105 | D / 2, H - G, FreeCAD.Vector(0, G, 0), FreeCAD.Vector(0, 1, 0)) 106 | z_socket = Part.makeCylinder( 107 | D / 2, H - G, FreeCAD.Vector(0, 0, G), FreeCAD.Vector(0, 0, 1)) 108 | return shape.fuse([x_socket, y_socket, z_socket]) 109 | 110 | @classmethod 111 | def createShape(cls, obj): 112 | outer = cls.createOuterPart(obj) 113 | inner = cls.createInnerPart(obj) 114 | return outer.cut(inner) 115 | 116 | def execute(self, obj): 117 | # Create the shape of the tee. 118 | shape = Corner.createShape(obj) 119 | obj.Shape = shape 120 | # Recalculate ports. 121 | obj.Ports = self.getPorts(obj) 122 | 123 | def getPorts(self, obj): 124 | """Calculate coordinates of the ports.""" 125 | dims = self.extractDimensions(obj) 126 | aux = dims.calculateAuxiliararyPoints() 127 | return [aux["p1"], aux["p2"], aux["p3"]] # x, y, z. 128 | 129 | @classmethod 130 | def getPortRotationAngles(cls, obj): 131 | """Calculate coordinates of the ports rotation and return them as vectorsself. 132 | 133 | x = Yaw 134 | y = Pitch 135 | z = Roll 136 | """ 137 | x = FreeCAD.Vector(0, 0, 0) 138 | y = FreeCAD.Vector(90, 0, 0) 139 | z = FreeCAD.Vector(0, -90, 0) 140 | return [x, y, z] 141 | 142 | 143 | class CornerBuilder: 144 | """Create a corner using Dodo/Flamingo.""" 145 | 146 | def __init__(self, document): 147 | self.dims = CornerMod.Dimensions() 148 | self.pos = FreeCAD.Vector(0, 0, 0) # Define default initial position. 149 | self.document = document 150 | 151 | def create(self, obj): 152 | """Create a corner. 153 | 154 | Before call it, call 155 | feature = self.document.addObject("Part::FeaturePython","OSE-Corner") 156 | """ 157 | corner = Corner(obj, PSize="", dims=self.dims) 158 | obj.ViewObject.Proxy = 0 159 | obj.Placement.Base = self.pos 160 | 161 | return corner 162 | 163 | # Create a test corner. 164 | 165 | 166 | def Test(): 167 | document = FreeCAD.activeDocument() 168 | builder = CornerBuilder(document) 169 | feature = document.addObject("Part::FeaturePython", "OSE-Corner") 170 | builder.create(feature) 171 | document.recompute() 172 | 173 | # Test() 174 | -------------------------------------------------------------------------------- /OsePiping/FlBushing.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Authors: Ruslan Krenzler, oddtopus. 3 | # Date: 25 March 2018 4 | # Create a bushing using Flamingo workbench. 5 | 6 | import FreeCAD 7 | import Part 8 | # Parent class from Dodo or Flamingo. 9 | try: 10 | from pFeatures import pypeType 11 | except ModuleNotFoundError: 12 | from pipeFeatures import pypeType 13 | import OsePiping.Bushing as BushingMod 14 | 15 | 16 | class Bushing(pypeType): 17 | def __init__(self, obj, PSize="", dims=BushingMod.Dimensions()): 18 | """Create a bushing.""" 19 | # Run parent __init__ and define common attributes 20 | super(Bushing, self).__init__(obj) 21 | obj.PType = "OSE_Bushing" 22 | obj.PRating = "BushingFittingFromAnyCatalog" 23 | obj.PSize = PSize # What is it for? 24 | # Define specific attributes and set their values. 25 | obj.addProperty("App::PropertyLength", "L", "Bushing", 26 | "Bushing length").L = dims.L 27 | obj.addProperty("App::PropertyLength", "N", "Bushing", "N").N = dims.N 28 | obj.addProperty("App::PropertyLength", "POD", "Bushing", 29 | "Large pipe outer diameter.").POD = dims.POD 30 | obj.addProperty("App::PropertyLength", "POD1", "Bushing", 31 | "Small pipe outer diameter.").POD1 = dims.POD1 32 | obj.addProperty("App::PropertyLength", "PThk1", "Bushing", 33 | "Small pipe thickness.").PThk1 = dims.PThk1 34 | 35 | # New Dodo-pypeType already contains Ports, use them. 36 | # But if pypeType does does not have Ports, add them. 37 | if "App::PropertyVectorList" not in obj.supportedProperties(): 38 | obj.addProperty("App::PropertyVectorList", "Ports", "Bushing", "Ports relative positions.") 39 | obj.Ports = self.getPorts(obj) 40 | 41 | obj.addProperty("App::PropertyVectorList", "PortRotationAngles", "Bushing", 42 | "Ports rotation angles.").PortRotationAngles = self.getPortRotationAngles(obj) 43 | obj.addProperty("App::PropertyString", "PartNumber", 44 | "Bushing", "Part number").PartNumber = "" 45 | # Make Ports read only. 46 | obj.setEditorMode("Ports", 1) 47 | obj.setEditorMode("PortRotationAngles", 1) 48 | 49 | def onChanged(self, obj, prop): 50 | # Attributes changed, adjust the rest. 51 | dim_properties = ["L", "N"] # Dimensions which change port locations 52 | if prop in dim_properties: 53 | # This function is called within __init__ too. 54 | # We wait for all dimension. 55 | if set(BushingMod.DIMENSIONS_USED).issubset(obj.PropertiesList): 56 | obj.Ports = self.getPorts(obj) 57 | # We also wait until PortRotationAngles are defined. 58 | # We do not need to call getPortRotationAngles, when the fitting is created, 59 | # because during the createion getPortRotationAngles is called directly. 60 | if hasattr(obj, "PortRotationAngles"): 61 | obj.PortRotationAngles = self.getPortRotationAngles(obj) 62 | 63 | @classmethod 64 | def extractDimensions(cls, obj): 65 | dims = BushingMod.Dimensions() 66 | dims.L = obj.L 67 | dims.N = obj.N 68 | dims.POD = obj.POD 69 | dims.POD1 = obj.POD1 70 | dims.PThk1 = obj.PThk1 71 | return dims 72 | 73 | @classmethod 74 | def createOctaThing(cls, obj): 75 | """Create Octagonal thing at the end of the bushing. I do not know its name.""" 76 | dims = cls.extractDimensions(obj) 77 | aux = dims.auxiliararyPoints() 78 | X1 = dims.ThingThicknessA1() 79 | X2 = dims.ThingLengthA2() 80 | # Move the box into the center of the X,Y plane. 81 | center_pos = FreeCAD.Vector(-X2 / 2, -X2 / 2, 0) + aux["p4"] 82 | box1 = Part.makeBox(X2, X2, X1, center_pos) 83 | # rotate a box by 45° around the z.axis. 84 | box2 = Part.makeBox(X2, X2, X1, center_pos) 85 | box2.rotate(FreeCAD.Vector(0, 0, 0), FreeCAD.Vector(0, 0, 1), 45) 86 | return box1.common(box2) 87 | 88 | @classmethod 89 | def createOuterPart(cls, obj): 90 | dims = cls.extractDimensions(obj) 91 | aux = dims.auxiliararyPoints() 92 | outer_cylinder = Part.makeCylinder(dims.POD / 2, dims.L, aux["p1"]) 93 | thing = Bushing.createOctaThing(obj) 94 | return outer_cylinder.fuse(thing) 95 | 96 | @classmethod 97 | def createInnerPart(cls, obj): 98 | dims = cls.extractDimensions(obj) 99 | aux = dims.auxiliararyPoints() 100 | 101 | # Remove inner part of the sockets. 102 | inner_cylinder = Part.makeCylinder(dims.PID1() / 2, dims.L, aux["p1"]) 103 | inner_socket = Part.makeCylinder( 104 | dims.POD1 / 2, dims.L - dims.N, aux["p3"]) 105 | 106 | # Make a cone for a larger socket. There are no dimensions for this con. Therefore 107 | # use simbolically a Radius such that the wall at the lower end is twice as thick 108 | # as in the upper end of socket. 109 | r1 = dims.POD / 2 - dims.ThicknessA3() 110 | r2 = dims.PID1() / 2 111 | hcone = dims.ConeLengthA4() 112 | socket_cone = Part.makeCone(r1, r2, hcone, aux["p1"]) 113 | 114 | return inner_cylinder.fuse([inner_socket, socket_cone]) 115 | 116 | @classmethod 117 | def createShape(cls, obj): 118 | outer = cls.createOuterPart(obj) 119 | inner = cls.createInnerPart(obj) 120 | return outer.cut(inner) 121 | 122 | def execute(self, obj): 123 | # Create the shape of the bushing. 124 | shape = self.createShape(obj) 125 | obj.Shape = shape 126 | # Recalculate ports. 127 | obj.Ports = self.getPorts(obj) 128 | 129 | def getPorts(self, obj): 130 | """Calculate coordinates of the ports.""" 131 | dims = self.extractDimensions(obj) 132 | aux = dims.auxiliararyPoints() 133 | # For the bottom port use p3 too. Because there is no a1 dimension in my specification. 134 | return[aux["p3"], aux["p3"]] 135 | 136 | @classmethod 137 | def getPortRotationAngles(cls, obj): 138 | """Calculate coordinates of the ports rotation and return them as vectorsself. 139 | 140 | x = Yaw 141 | y = Pitch 142 | z = Roll 143 | """ 144 | outer = FreeCAD.Vector(0, 90, 0) 145 | inner = FreeCAD.Vector(0, -90, 0) 146 | 147 | return [outer, inner] 148 | 149 | 150 | class BushingBuilder: 151 | """Create a bushing using Dodo/Flamingo.""" 152 | 153 | def __init__(self, document): 154 | self.dims = BushingMod.Dimensions() 155 | self.pos = FreeCAD.Vector(0, 0, 0) # Define default initial position. 156 | self.document = document 157 | 158 | def create(self, obj): 159 | """Create a bushing. 160 | 161 | Before call it, call 162 | feature = self.document.addObject("Part::FeaturePython","OSE-Bushing") 163 | """ 164 | bushing = Bushing(obj, PSize="", dims=self.dims) 165 | obj.ViewObject.Proxy = 0 166 | obj.Placement.Base = self.pos 167 | 168 | return bushing 169 | 170 | # Create a test bushing. 171 | 172 | 173 | def Test(): 174 | document = FreeCAD.activeDocument() 175 | builder = BushingBuilder(document) 176 | feature = document.addObject("Part::FeaturePython", "OSE-Bushing") 177 | builder.create(feature) 178 | document.recompute() 179 | 180 | # Test() 181 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | 167 | -------------------------------------------------------------------------------- /Resources/icons/CreatePipe.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 21 | 23 | 26 | 30 | 34 | 35 | 46 | 48 | 52 | 56 | 57 | 68 | 75 | 86 | 97 | 100 | 104 | 108 | 109 | 119 | 120 | 140 | 147 | 148 | 150 | 151 | 153 | image/svg+xml 154 | 156 | 157 | 158 | [wmayer] 159 | 160 | 161 | Part_Cylinder 162 | 2011-10-10 163 | http://www.freecadweb.org/wiki/index.php?title=Artwork 164 | 165 | 166 | FreeCAD 167 | 168 | 169 | FreeCAD/src/Mod/Part/Gui/Resources/icons/Part_Cylinder.svg 170 | 171 | 172 | FreeCAD LGPL2+ 173 | 174 | 175 | https://www.gnu.org/copyleft/lesser.html 176 | 177 | 178 | [agryson] Alexander Gryson 179 | 180 | 181 | 182 | 183 | 184 | 188 | 194 | 200 | 206 | 213 | 220 | 221 | 222 | -------------------------------------------------------------------------------- /OsePiping/FlCross.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Authors: oddtopus, Ruslan Krenzler 3 | # Date: 8. April 2018 4 | 5 | # Create a cross-fitting using Flamingo workbench. 6 | 7 | import FreeCAD 8 | import Part 9 | # Parent class from Dodo or Flamingo. 10 | try: 11 | from pFeatures import pypeType 12 | except ModuleNotFoundError: 13 | from pipeFeatures import pypeType 14 | import OsePiping.Cross as CrossMod 15 | 16 | 17 | class Cross(pypeType): 18 | def __init__(self, obj, PSize="90degBend20x10", dims=CrossMod.Dimensions()): 19 | # run parent __init__ and define common attributes 20 | super(Cross, self).__init__(obj) 21 | obj.PType = "OSE_Cross" 22 | obj.PRating = "CrossFittingFromAnyCatalog" 23 | obj.PSize = PSize # Pipe size 24 | # define specific attributes 25 | # Properties 26 | obj.addProperty("App::PropertyLength", "G", "Cross", 27 | "G dimension of the cross.").G = dims.G 28 | obj.addProperty("App::PropertyLength", "G1", "Cross", 29 | "G1 dimension of the cross.").G1 = dims.G1 30 | obj.addProperty("App::PropertyLength", "H", "Cross", 31 | "Distance from the center to the left end.").H = dims.H 32 | obj.addProperty("App::PropertyLength", "H1", "Cross", 33 | "Dimension from the center to the bottom end.").H1 = dims.H1 34 | obj.addProperty("App::PropertyLength", "L", "Cross", 35 | "Horizontal length of the cross.").L = dims.L 36 | obj.addProperty("App::PropertyLength", "L1", "Cross", 37 | "Vertical length of the cross.").L1 = dims.L1 38 | obj.addProperty("App::PropertyLength", "M", "Cross", 39 | "Outer diameter of the horizonal part.").M = dims.M 40 | obj.addProperty("App::PropertyLength", "M1", "Cross", 41 | "Outer diameter of the vertical part.").M1 = dims.M1 42 | obj.addProperty("App::PropertyLength", "POD", "Cross", 43 | "Pipe outer diameter of the horizontal part.").POD = dims.POD 44 | obj.addProperty("App::PropertyLength", "POD1", "Cross", 45 | "Pipe outer Diameter of the vertical part.").POD1 = dims.POD1 46 | obj.addProperty("App::PropertyLength", "PThk", "Cross", 47 | "Pipe wall thickness of the horizonal part").PThk = dims.PThk 48 | obj.addProperty("App::PropertyLength", "PThk1", "Cross", 49 | "Pipe wall thickness of the vertical part").PThk1 = dims.PThk1 50 | obj.addProperty("App::PropertyString", "PartNumber", 51 | "Cross", "Part number").PartNumber = "" 52 | 53 | # New Dodo-pypeType already contains Ports, use them. 54 | # But if pypeType does does not have Ports, add them. 55 | if "App::PropertyVectorList" not in obj.supportedProperties(): 56 | obj.addProperty("App::PropertyVectorList", "Ports", "Cross", "Ports relative positions.") 57 | obj.Ports = self.getPorts(obj) 58 | 59 | obj.addProperty("App::PropertyVectorList", "PortRotationAngles", "Cross", 60 | "Ports rotation angles.").PortRotationAngles = self.getPortRotationAngles(obj) 61 | # Make Ports read only. 62 | obj.setEditorMode("Ports", 1) 63 | obj.setEditorMode("PortRotationAngles", 1) 64 | 65 | def onChanged(self, obj, prop): 66 | # if you aim to do something when an attribute is changed 67 | # place the code here: 68 | # e.g. -> change PSize according the new alpha, PID and POD 69 | 70 | # Properties which can change port locations 71 | dim_properties = ["G", "G1"] 72 | 73 | if prop in dim_properties: 74 | # This function is called within __init__ too. Thus we need to wait until 75 | # we have all dimensions attributes. 76 | if set(CrossMod.DIMENSIONS_USED).issubset(obj.PropertiesList): 77 | obj.Ports = self.getPorts(obj) 78 | # Wait until PortRotationAngles are defined. 79 | if hasattr(obj, "PortRotationAngles"): 80 | obj.PortRotationAngles = self.getPortRotationAngles(obj) 81 | 82 | @classmethod 83 | def extractDimensions(cls, obj): 84 | dims = CrossMod.Dimensions() 85 | dims.G = obj.G 86 | dims.G1 = obj.G1 87 | dims.H = obj.H 88 | dims.H1 = obj.H1 89 | dims.L = obj.L 90 | dims.L1 = obj.L1 91 | dims.M = obj.M 92 | dims.M1 = obj.M1 93 | dims.POD = obj.POD 94 | dims.POD1 = obj.POD1 95 | dims.PThk = obj.PThk 96 | dims.PThk1 = obj.PThk1 97 | return dims 98 | 99 | @classmethod 100 | def createOuterPart(cls, obj): 101 | dims = cls.extractDimensions(obj) 102 | aux = dims.calculateAuxiliararyPoints() 103 | hr = dims.M / 2 104 | hor_cylinder = Part.makeCylinder( 105 | hr, dims.L, aux["p1"], FreeCAD.Vector(1, 0, 0)) 106 | vr = dims.M1 / 2 107 | vert_cylinder = Part.makeCylinder(vr, dims.L1, aux["p4"]) 108 | outer = hor_cylinder.fuse(vert_cylinder) 109 | return outer 110 | 111 | @classmethod 112 | def createInnerPart(cls, obj): 113 | dims = cls.extractDimensions(obj) 114 | aux = dims.calculateAuxiliararyPoints() 115 | p1 = aux["p1"] 116 | p3 = aux["p3"] 117 | p4 = aux["p4"] 118 | p6 = aux["p6"] 119 | pid = dims.PID() 120 | pid1 = dims.PID1() 121 | pod = dims.POD 122 | pod1 = dims.POD1 123 | hr = pid / 2 124 | hor_cylinder = Part.makeCylinder( 125 | hr, dims.L, p1, FreeCAD.Vector(1, 0, 0)) 126 | vr = pid1 / 2 127 | vert_cylinder = Part.makeCylinder(vr, dims.L1, p4) 128 | 129 | # Create sockets. 130 | socket_left = Part.makeCylinder( 131 | pod / 2, dims.socketDepthLeft(), p1, FreeCAD.Vector(1, 0, 0)) 132 | socket_right = Part.makeCylinder( 133 | pod / 2, dims.socketDepthRight(), p3, FreeCAD.Vector(1, 0, 0)) 134 | socket_bottom = Part.makeCylinder( 135 | pod1 / 2, dims.socketDepthBottom(), p4) 136 | socket_top = Part.makeCylinder(pod1 / 2, dims.socketDepthTop(), p6) 137 | 138 | # Combine all cylinders. 139 | inner = hor_cylinder.fuse([vert_cylinder, socket_left, 140 | socket_right, socket_bottom, socket_top]) 141 | return inner 142 | 143 | @classmethod 144 | def createShape(cls, obj): 145 | outer = cls.createOuterPart(obj) 146 | inner = cls.createInnerPart(obj) 147 | return outer.cut(inner) 148 | # return outer 149 | 150 | def execute(self, obj): 151 | # Create the shape of the tee. 152 | shape = self.createShape(obj) 153 | obj.Shape = shape 154 | # Recalculate ports. 155 | obj.Ports = self.getPorts(obj) 156 | 157 | def getPorts(self, obj): 158 | dims = Cross.extractDimensions(obj) 159 | aux = dims.calculateAuxiliararyPoints() 160 | return [aux["p2"], aux["p3"], aux["p5"], aux["p6"]] 161 | 162 | @classmethod 163 | def getPortRotationAngles(cls, obj): 164 | """Calculate coordinates of the ports rotation and return them as vectorsself. 165 | 166 | x = Yaw 167 | y = Pitch 168 | z = Roll 169 | """ 170 | left = FreeCAD.Vector(180, 0, 180) 171 | right = FreeCAD.Vector(0, 0, 0) 172 | bottom = FreeCAD.Vector(0, 90, 0) 173 | top = FreeCAD.Vector(0, -90, 0) 174 | return [left, right, bottom, top] 175 | 176 | 177 | class CrossBuilder: 178 | """Create a cross using Dodo/Flamingo.""" 179 | 180 | def __init__(self, document): 181 | self.dims = CrossMod.Dimensions() 182 | self.pos = FreeCAD.Vector(0, 0, 0) # Define default initial position. 183 | self.document = document 184 | 185 | def create(self, obj): 186 | """Create a cross.""" 187 | cross = Cross(obj, PSize="", dims=self.dims) 188 | obj.ViewObject.Proxy = 0 189 | obj.Placement.Base = self.pos 190 | # rot=FreeCAD.Rotation(FreeCAD.Vector(0,0,1), self.Z) 191 | # obj.Placement.Rotation=rot.multiply(obj.Placement.Rotation) 192 | # feature.ViewObject.Transparency=70 193 | return cross 194 | 195 | # Test builder. 196 | 197 | 198 | def TestPart(): 199 | document = FreeCAD.activeDocument() 200 | builder = CrossBuilder(document) 201 | feature = document.addObject("Part::FeaturePython", "OSE-Cross") 202 | builder.create(feature) 203 | document.recompute() 204 | 205 | # TestPart() 206 | -------------------------------------------------------------------------------- /OsePiping/Corner.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Author: Ruslan Krenzler. 3 | # Date: 09 February 2018 4 | # Create a corner-fitting. 5 | 6 | import os.path 7 | import FreeCAD 8 | import OsePipingBase 9 | import OsePiping.Piping as Piping 10 | 11 | parseQuantity = FreeCAD.Units.parseQuantity 12 | 13 | # This is the path to the dimensions table. 14 | CSV_TABLE_PATH = os.path.join(OsePipingBase.TABLE_PATH, "corner.csv") 15 | # It must contain unique values in the column "PartNumber" and also, dimensions listened below. 16 | DIMENSIONS_USED = ["G", "H", "M", "POD", "PThk"] 17 | 18 | 19 | class Dimensions: 20 | def __init__(self): 21 | self.G = parseQuantity("2 cm") 22 | self.H = parseQuantity("3 cm") 23 | self.M = parseQuantity("3 cm") 24 | self.POD = parseQuantity("2 cm") 25 | self.PThk = parseQuantity("0.5 cm") 26 | 27 | def isValid(self): 28 | errorMsg = "" 29 | if not (self.POD > 0): 30 | errorMsg = "Pipe outer diameter POD {} must be positive.".format( 31 | self.POD) 32 | elif not (self.PThk <= self.POD / 2.0): 33 | errorMsg = "Pipe thickness PThk {} is too large: larger than POD/2 {}.".foramt( 34 | self.PThk, self.POD / 2.0) 35 | elif not (self.M > self.POD): 36 | errorMsg = "Outer diameter M {} must be larger than outer pipe diameter POD {}".format( 37 | self.M, self.POD) 38 | elif not (self.G > 0): 39 | errorMsg = "Length G {} be positive".format(self.G) 40 | elif not (self.H > self.G): 41 | errorMsg = "Length H {} must be larger than length G {}".format( 42 | self.H, self.G) 43 | if not (self.G > self.PID() / 2.0): 44 | errorMsg = "Length G {} must be larger than inner pipe radius PID/2={}.".format( 45 | self.G, self.PID() / 2.0) 46 | 47 | return (len(errorMsg) == 0, errorMsg) 48 | 49 | def PID(self): 50 | return self.POD - 2 * self.PThk 51 | 52 | def calculateAuxiliararyPoints(self): 53 | """Calculate auxiliarary points which are used to build a corner from cylinders. 54 | 55 | See documentation picture corner-cacluations.png. 56 | """ 57 | result = {} 58 | result["p1"] = FreeCAD.Vector(self.G, 0, 0) 59 | result["p2"] = FreeCAD.Vector(0, self.G, 0) 60 | result["p3"] = FreeCAD.Vector(0, 0, self.G) 61 | return result 62 | 63 | 64 | class Corner: 65 | def __init__(self, document): 66 | self.document = document 67 | # Set default values. 68 | self.dims = Dimensions() 69 | 70 | def checkDimensions(self): 71 | valid, msg = self.dims.isValid() 72 | if not valid: 73 | raise Piping.UnplausibleDimensions(msg) 74 | 75 | def createPrimitiveCorner(self, L, D): 76 | """Create corner consisting of two cylinder along x-,y- and y axis and a ball in the center.""" 77 | x_cylinder = self.document.addObject("Part::Cylinder", "XCynlider") 78 | x_cylinder.Radius = D / 2 79 | x_cylinder.Height = L 80 | x_cylinder.Placement = FreeCAD.Placement(FreeCAD.Vector( 81 | 0, 0, 0), FreeCAD.Rotation(FreeCAD.Vector(0, 1, 0), 90), FreeCAD.Vector(0, 0, 0)) 82 | y_cylinder = self.document.addObject("Part::Cylinder", "YCynlider") 83 | y_cylinder.Radius = D / 2 84 | y_cylinder.Height = L 85 | y_cylinder.Placement = FreeCAD.Placement(FreeCAD.Vector(0, 0, 0), FreeCAD.Rotation( 86 | FreeCAD.Vector(1, 0, 0), -90), FreeCAD.Vector(0, 0, 0)) 87 | z_cylinder = self.document.addObject("Part::Cylinder", "ZCynlider") 88 | z_cylinder.Radius = D / 2 89 | z_cylinder.Height = L 90 | sphere = self.document.addObject("Part::Sphere", "Sphere") 91 | sphere.Radius = D / 2 92 | fusion = self.document.addObject("Part::MultiFuse", "Fusion") 93 | fusion.Shapes = [x_cylinder, y_cylinder, z_cylinder, sphere] 94 | return fusion 95 | 96 | def addSockets(self, fusion): 97 | """Add socket cylinders to the fusion.""" 98 | x_socket = self.document.addObject("Part::Cylinder", "XSocket") 99 | x_socket.Radius = self.dims.POD / 2 100 | x_socket.Height = self.dims.H - self.dims.G 101 | x_socket.Placement = FreeCAD.Placement(FreeCAD.Vector(self.dims.G, 0, 0), FreeCAD.Rotation( 102 | FreeCAD.Vector(0, 1, 0), 90), FreeCAD.Vector(0, 0, 0)) 103 | y_socket = self.document.addObject("Part::Cylinder", "YSocket") 104 | y_socket.Radius = self.dims.POD / 2 105 | y_socket.Height = self.dims.H - self.dims.G 106 | y_socket.Placement = FreeCAD.Placement(FreeCAD.Vector(0, self.dims.G, 0), FreeCAD.Rotation( 107 | FreeCAD.Vector(1, 0, 0), -90), FreeCAD.Vector(0, 0, 0)) 108 | z_socket = self.document.addObject("Part::Cylinder", "ZSocket") 109 | z_socket.Radius = self.dims.POD / 2 110 | z_socket.Height = self.dims.H - self.dims.G 111 | z_socket.Placement.Base = FreeCAD.Vector(0, 0, self.dims.G) 112 | # fusion.Shapes.append does not work. 113 | fusion.Shapes = fusion.Shapes + [x_socket, y_socket, z_socket] 114 | return fusion 115 | 116 | def createOuterPart(self): 117 | return self.createPrimitiveCorner(self.dims.H, self.dims.M) 118 | 119 | def createInnerPart(self): 120 | inner = self.createPrimitiveCorner(self.dims.H, self.dims.PID()) 121 | inner = self.addSockets(inner) 122 | return inner 123 | 124 | def create(self, convertToSolid): 125 | self.checkDimensions() 126 | outer = self.createOuterPart() 127 | inner = self.createInnerPart() 128 | # Remove inner part of the sockets. 129 | corner = self.document.addObject("Part::Cut", "Cut") 130 | corner.Base = outer 131 | corner.Tool = inner 132 | 133 | if convertToSolid: 134 | # Before making a solid, recompute documents. Otherwise there will be 135 | # s = Part.Solid(Part.Shell(s)) 136 | # : Shape is null 137 | # exception. 138 | self.document.recompute() 139 | # Now convert all parts to solid, and remove intermediate data. 140 | solid = Piping.toSolid(self.document, corner, "corner (solid)") 141 | Piping.removePartWithChildren(self.document, corner) 142 | return solid 143 | return corner 144 | 145 | 146 | class CornerFromTable: 147 | """Create a part with dimensions from a CSV table.""" 148 | 149 | def __init__(self, document, table): 150 | self.document = document 151 | self.table = table 152 | 153 | @classmethod 154 | def getPThk(cls, row): 155 | """For compatibility results, if there is no "PThk" dimension, calculate it from "PID" and "POD".""" 156 | if "PThk" not in row.keys(): 157 | return (parseQuantity(row["POD"]) - parseQuantity(row["PID"])) / 2.0 158 | else: 159 | return parseQuantity(row["PThk"]) 160 | 161 | @classmethod 162 | def getPSize(cls, row): 163 | if "PSize" in row.keys(): 164 | return row["PSize"] 165 | else: 166 | return "" 167 | 168 | def create(self, partNumber, outputType): 169 | row = self.table.findPart(partNumber) 170 | if row is None: 171 | print("Part not found") 172 | return 173 | 174 | dims = Dimensions() 175 | dims.G = parseQuantity(row["G"]) 176 | dims.H = parseQuantity(row["H"]) 177 | dims.M = parseQuantity(row["M"]) 178 | dims.POD = parseQuantity(row["POD"]) 179 | dims.PThk = self.getPThk(row) 180 | 181 | if outputType == Piping.OUTPUT_TYPE_PARTS or outputType == Piping.OUTPUT_TYPE_SOLID: 182 | corner = Corner(self.document) 183 | corner.dims = dims 184 | part = corner.create(outputType == Piping.OUTPUT_TYPE_SOLID) 185 | part.Label = "OSE-Corner" 186 | return part 187 | 188 | elif outputType == Piping.OUTPUT_TYPE_DODO_OR_FLAMINGO: 189 | feature = self.document.addObject( 190 | "Part::FeaturePython", "OSE-Corner") 191 | import OsePiping.FlCorner as FlCorner 192 | builder = FlCorner.CornerBuilder(self.document) 193 | builder.dims = dims 194 | part = builder.create(feature) 195 | feature.PRating = Piping.GetPressureRatingString(row) 196 | feature.PSize = self.getPSize(row) 197 | feature.ViewObject.Proxy = 0 198 | feature.PartNumber = partNumber 199 | return part 200 | 201 | 202 | # Test macros. 203 | def TestCorner(): 204 | document = FreeCAD.activeDocument() 205 | corner = Corner(document) 206 | corner.create(True) 207 | document.recompute() 208 | 209 | 210 | def TestTable(): 211 | document = FreeCAD.activeDocument() 212 | table = Piping.CsvTable(DIMENSIONS_USED) 213 | table.load(CSV_TABLE_PATH) 214 | builder = CornerFromTable(document, table) 215 | for i in range(0, len(table.data)): 216 | print("Selecting row %d" % i) 217 | partNumber = table.getPartKey(i) 218 | print("Creating part %s" % partNumber) 219 | builder.create(partNumber, Piping.OUTPUT_TYPE_SOLID) 220 | document.recompute() 221 | 222 | 223 | # TestCorner() 224 | # TestTable() 225 | -------------------------------------------------------------------------------- /OsePiping/FlSweepElbow.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Authors: Ruslan Krenzler, oddtopus 3 | # Date: 30. March 2018 4 | # Create a sweep-elbow-fitting using flamingo workbench 5 | 6 | import FreeCAD 7 | import Part 8 | # Parent class from Dodo or Flamingo. 9 | try: 10 | from pFeatures import pypeType 11 | except ModuleNotFoundError: 12 | from pipeFeatures import pypeType 13 | import OsePiping.SweepElbow as SweepElbowMod 14 | 15 | 16 | # The value RELATIVE_EPSILON is used to slightly change the size of parts 17 | # to prevent problems with boolean operations. 18 | # Keep this value very small. 19 | # For example, the outer bent part of the elbow dissaperas when it has 20 | # the same radius as the cylinder at the ends. 21 | RELATIVE_EPSILON = 0.000001 22 | 23 | 24 | class SweepElbow(pypeType): 25 | def __init__(self, obj, PSize="", dims=SweepElbowMod.Dimensions()): 26 | """Create a sweep elbow with the center at (0,0,0) sockets along the z and y axis.""" 27 | # Run parent __init__ and define common attributes. 28 | super(SweepElbow, self).__init__(obj) 29 | obj.PType = "OSE_SweepElbow" 30 | obj.PRating = "" 31 | obj.PSize = PSize 32 | # Define specific attributes and set their values. 33 | obj.addProperty("App::PropertyAngle", "BendAngle", 34 | "SweepElbow", "Bend Angle.").BendAngle = dims.BendAngle 35 | obj.addProperty("App::PropertyLength", "J", "SweepElbow", 36 | "Distnace from the center to begin of innerpart of the socket").J = dims.J 37 | obj.addProperty("App::PropertyLength", "H", "SweepElbow", 38 | "Distance between the center and a elbow end").H = dims.H 39 | obj.addProperty("App::PropertyLength", "M", "SweepElbow", 40 | "Outer diameter of the elbow.").M = dims.M 41 | obj.addProperty("App::PropertyLength", "POD", "SweepElbow", 42 | "Pipe outer diameter.").POD = dims.POD 43 | obj.addProperty("App::PropertyLength", "PThk", 44 | "SweepElbow", "Pipe wall thickness").PThk = dims.PThk 45 | 46 | # New Dodo-pypeType already contains Ports, use them. 47 | # But if pypeType does does not have Ports, add them. 48 | if "App::PropertyVectorList" not in obj.supportedProperties(): 49 | obj.addProperty("App::PropertyVectorList", "Ports", "SweepElbow", "Ports relative positions.") 50 | obj.Ports = self.getPorts(obj) 51 | 52 | obj.addProperty("App::PropertyVectorList", "PortRotationAngles", "SweepElbow", 53 | "Ports rotation angles.").PortRotationAngles = self.getPortRotationAngles(obj) 54 | obj.addProperty("App::PropertyString", "PartNumber", 55 | "SweepElbow", "Part number").PartNumber = "" 56 | 57 | # Make Ports read only. 58 | obj.setEditorMode("Ports", 1) 59 | obj.setEditorMode("PortRotationAngles", 1) 60 | 61 | def onChanged(self, obj, prop): 62 | # if you aim to do something when an attribute is changed 63 | # place the code here: 64 | # e.g. -> change PSize according the new alpha, PID and POD 65 | 66 | # Dimensions which can change port positions. 67 | dim_properties = ["BendAngle", "J"] 68 | if prop in dim_properties: 69 | # This function is called within __init__ too. 70 | # We wait for all dimension. 71 | if set(SweepElbowMod.DIMENSIONS_USED).issubset(obj.PropertiesList): 72 | obj.Ports = self.getPorts(obj) 73 | # Wait until PortRotationAngles are defined. 74 | if hasattr(obj, "PortRotationAngles"): 75 | obj.PortRotationAngles = self.getPortRotationAngles(obj) 76 | 77 | @staticmethod 78 | def extractDimensions(obj): 79 | dims = SweepElbowMod.Dimensions() 80 | dims.BendAngle = obj.BendAngle 81 | dims.H = obj.H 82 | dims.J = obj.J 83 | dims.M = obj.M 84 | dims.POD = obj.POD 85 | dims.PThk = obj.PThk 86 | return dims 87 | 88 | @staticmethod 89 | def createBentCylinder(obj, rCirc): 90 | """Create a cylinder of radius rCirc in x-y plane which is bent in the middle. 91 | 92 | :param group: Group where to add created objects. 93 | :param rCirc: Radius of the cylinder. 94 | 95 | See documentation picture sweep-elbow-cacluations.png. 96 | """ 97 | # Convert alpha to degree value 98 | dims = SweepElbow.extractDimensions(obj) 99 | 100 | aux = dims.calculateAuxiliararyPoints() 101 | 102 | alpha = float(dims.BendAngle.getValueAs("deg")) 103 | rBend = (aux["p3"] - aux["p5"]).Length 104 | 105 | # Put a base on the streight part. 106 | base = Part.makeCircle(rCirc, aux["p5"], aux["p5"]) 107 | 108 | # Add trajectory 109 | trajectory = Part.makeCircle( 110 | rBend, aux["p3"], FreeCAD.Vector(0, 0, 1), 225 - alpha / 2, 225 + alpha / 2) 111 | # Show trajectory for debugging. 112 | # W = W1.fuse([trajectory.Edges]) 113 | # Part.Show(W) 114 | # Add a cap (circle, at the other end of the bent cylinder). 115 | cap = Part.makeCircle(rCirc, aux["p6"], aux["p6"]) 116 | # Sweep the circle along the trajectory. 117 | sweep = Part.makeSweepSurface(trajectory, base) 118 | # The sweep is only a 2D service consisting of walls only. 119 | # Add circles on both ends of this wall. 120 | end1 = Part.Face(Part.Wire(base)) 121 | # Create other end. 122 | end2 = Part.Face(Part.Wire(cap)) 123 | solid = Part.Solid(Part.Shell([end1, sweep, end2])) 124 | return solid 125 | 126 | @staticmethod 127 | def createOuterPart(obj): 128 | dims = SweepElbow.extractDimensions(obj) 129 | aux = dims.calculateAuxiliararyPoints() 130 | # Make the outer part slightly larger. Otherwise it can be shown incorrectly after 131 | # the subtraction of the inner part. 132 | r = ((dims.PID() / 2 + dims.fitThk()) * (1 + RELATIVE_EPSILON)) 133 | bentPart = SweepElbow.createBentCylinder(obj, r) 134 | # Create socket along the z axis. 135 | h = float(dims.H) - aux["p2"].Length 136 | r = dims.M / 2 137 | socket1 = Part.makeCylinder(r, h, aux["p2"], aux["p2"]) 138 | # Create socket along the bent part. 139 | socket2 = Part.makeCylinder(r, h, aux["p4"], aux["p4"]) 140 | 141 | outer = bentPart.fuse([socket1, socket2]) 142 | return outer 143 | 144 | @staticmethod 145 | def createInnerPart(obj): 146 | dims = SweepElbow.extractDimensions(obj) 147 | aux = dims.calculateAuxiliararyPoints() 148 | 149 | r = dims.POD / 2 - dims.PThk 150 | 151 | bentPart = SweepElbow.createBentCylinder( 152 | obj, r * (1 + RELATIVE_EPSILON)) 153 | 154 | rSocket = dims.POD / 2 155 | # The socket length is actually dims.H - dims.J. But we do it longer 156 | # to prevent problems with bulean operations 157 | hSocket = dims.H 158 | socket1 = Part.makeCylinder(rSocket, hSocket, aux["p5"], aux["p5"]) 159 | socket2 = Part.makeCylinder(rSocket, hSocket, aux["p6"], aux["p6"]) 160 | 161 | inner = bentPart.fuse([socket1, socket2]) 162 | return inner 163 | 164 | @staticmethod 165 | def createShape(obj): 166 | outer = SweepElbow.createOuterPart(obj) 167 | inner = SweepElbow.createInnerPart(obj) 168 | return outer.cut(inner) 169 | 170 | def execute(self, obj): 171 | # Create the shape of the tee. 172 | shape = SweepElbow.createShape(obj) 173 | obj.Shape = shape 174 | # Recalculate ports. 175 | obj.Ports = self.getPorts(obj) 176 | 177 | def getPorts(self, obj): 178 | dims = SweepElbow.extractDimensions(obj) 179 | aux = dims.calculateAuxiliararyPoints() 180 | # FreeCAD.Console.PrintMessage("Ports are %s and %s"%(aux["p5"], aux["p6"])) 181 | return [aux["p5"], aux["p6"]] 182 | 183 | def getPortRotationAngles(self, obj): 184 | """Calculate coordinates of the ports rotation and return them as vectorsself. 185 | 186 | x = Yaw 187 | y = Pitch 188 | z = Roll 189 | """ 190 | dims = SweepElbow.extractDimensions(obj) 191 | half = dims.BendAngle / 2 192 | # -45° and 135° are rotation of 0° elbow. They acts as a refence for a bent elbow. 193 | end0 = FreeCAD.Vector(-45 + half.Value, 0, 0) 194 | end1 = FreeCAD.Vector(135 - half.Value, 0, 0) 195 | return [end0, end1] 196 | 197 | 198 | class SweepElbowBuilder: 199 | """Create a sweep elbow using Dodo/flamingo.""" 200 | 201 | def __init__(self, document): 202 | self.dims = SweepElbowMod.Dimensions() 203 | self.pos = FreeCAD.Vector(0, 0, 0) # Define default initial position. 204 | self.document = document 205 | 206 | def create(self, obj): 207 | """Create a sweep elbow. 208 | 209 | Before call it, call 210 | feature = self.document.addObject("Part::FeaturePython","OSE-SweepElbow") 211 | """ 212 | elbow = SweepElbow(obj, PSize="", dims=self.dims) 213 | obj.ViewObject.Proxy = 0 214 | obj.Placement.Base = self.pos 215 | return elbow 216 | 217 | 218 | # Test builder. 219 | def TestSweepElbow(): 220 | document = FreeCAD.activeDocument() 221 | builder = SweepElbowBuilder(document) 222 | feature = document.addObject("Part::FeaturePython", "OSE-SweepElbow") 223 | builder.create(feature) 224 | document.recompute() 225 | 226 | # TestSweepElbow() 227 | -------------------------------------------------------------------------------- /OsePiping/FlCoupling.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Authors: Ruslan Krenzler, oddtopus. 3 | # Date: 24 March 2018 4 | # Create a coupling using Flamingo workbench. 5 | 6 | import FreeCAD 7 | import Part 8 | # Parent class from Dodo or Flamingo. 9 | try: 10 | from pFeatures import pypeType 11 | except ModuleNotFoundError: 12 | from pipeFeatures import pypeType 13 | import OsePiping.Coupling as CouplingMod 14 | 15 | 16 | class Coupling(pypeType): 17 | 18 | def __init__(self, obj, PSize="", dims=CouplingMod.Dimensions()): 19 | """Create a coupling.""" 20 | # Run parent __init__ and define common attributes 21 | super(Coupling, self).__init__(obj) 22 | obj.PType = "OSE_Coupling" 23 | obj.PRating = "CouplingFittingFromAnyCatalog" 24 | obj.PSize = PSize # Pipe size 25 | # Define specific attributes and set their values. 26 | # TODO: Check socket enumerations. 27 | obj.addProperty("App::PropertyLength", "L", "Coupling", 28 | "Length of the coupling").L = dims.L 29 | obj.addProperty("App::PropertyLength", "M", "Coupling", 30 | "Coupling outside diameter.").M = dims.M 31 | obj.addProperty("App::PropertyLength", "M1", "Coupling", 32 | "Coupling outside diameter of the thin end.").M1 = dims.M1 33 | obj.addProperty("App::PropertyLength", "N", "Coupling", 34 | "Length of the middle part of the coupling.").N = dims.N 35 | obj.addProperty("App::PropertyLength", "POD", "Coupling", 36 | "Pipe outer diameter at the socket 1.").POD = dims.POD 37 | obj.addProperty("App::PropertyLength", "POD1", "Coupling", 38 | "Pipe outer diameter at the socket 2.").POD1 = dims.POD1 39 | obj.addProperty("App::PropertyLength", "PThk", "Coupling", 40 | "Thickness of the pipe at the socket 1.").PThk = dims.PThk 41 | obj.addProperty("App::PropertyLength", "PThk1", "Coupling", 42 | "Thickness of the pipe at the socket 2.").PThk1 = dims.PThk1 43 | 44 | # New Dodo-pypeType already contains Ports, use them. 45 | # But if pypeType does does not have Ports, add them. 46 | if "App::PropertyVectorList" not in obj.supportedProperties(): 47 | obj.addProperty("App::PropertyVectorList", "Ports", "Coupling", "Ports relative positions.") 48 | obj.Ports = self.getPorts(obj) 49 | 50 | obj.addProperty("App::PropertyVectorList", "PortRotationAngles", "Coupling", 51 | "Ports rotation angles.").PortRotationAngles = self.getPortRotationAngles(obj) 52 | obj.addProperty("App::PropertyString", "PartNumber", 53 | "Coupling", "Part number").PartNumber = "" 54 | # Make Ports read only. 55 | obj.setEditorMode("Ports", 1) 56 | obj.setEditorMode("PortRotationAngles", 1) 57 | 58 | def onChanged(self, obj, prop): 59 | # if you aim to do something when an attribute is changed 60 | # place the code here: 61 | # e.g. -> change PSize according the new alpha, PID and POD 62 | 63 | dim_properties = ["L", "M", "M1", "N"] 64 | 65 | if prop in dim_properties: 66 | # This function is called within __init__ too. 67 | # We wait for all dimension. 68 | if set(CouplingMod.DIMENSIONS_USED).issubset(obj.PropertiesList): 69 | obj.Ports = self.getPorts(obj) 70 | # Wait until PortRotationAngles are defined. 71 | if hasattr(obj, "PortRotationAngles"): 72 | obj.PortRotationAngles = self.getPortRotationAngles(obj) 73 | 74 | @classmethod 75 | def extractDimensions(cls, obj): 76 | dims = CouplingMod.Dimensions() 77 | dims.L = obj.L 78 | dims.M = obj.M 79 | dims.M1 = obj.M1 80 | dims.N = obj.N 81 | dims.POD = obj.POD 82 | dims.POD1 = obj.POD1 83 | dims.PThk = obj.PThk 84 | dims.PThk1 = obj.PThk1 85 | return dims 86 | 87 | @classmethod 88 | def createOuterPart(cls, obj): 89 | dims = cls.extractDimensions(obj) 90 | 91 | if dims.M == dims.M1: 92 | return cls.createOuterPartEqual(obj) 93 | else: 94 | return cls.createOuterPartReduced(obj) 95 | 96 | @classmethod 97 | def createOuterPartEqual(cls, obj): 98 | """Create the outer part is a simple cylinder. This is when M and M1 are the equal.""" 99 | dims = cls.extractDimensions(obj) 100 | aux = dims.calculateAuxiliararyPoints() 101 | # Create complete outer cylinder. 102 | radius = dims.M / 2.0 103 | height = dims.L 104 | outer = Part.makeCylinder(radius, height, aux["p1"]) 105 | return outer 106 | 107 | @classmethod 108 | def createOuterPartReduced(cls, obj): 109 | """Create a outer part which is cylinder+cone+cylinder.""" 110 | dims = cls.extractDimensions(obj) 111 | aux = dims.calculateAuxiliararyPoints() 112 | # Create socket 1. 113 | r1 = dims.M / 2.0 114 | h1 = dims.bottomSocketOuterLength() 115 | cylinder1 = Part.makeCylinder(r1, h1, aux["p1"]) 116 | # Create a cone and put it on the cylinder 1. 117 | r2 = dims.M1 / 2.0 118 | hc = dims.N 119 | cone = Part.makeCone(r1, r2, hc, aux["p4"]) 120 | # Create a socket 2 and put it on the cone. 121 | h2 = dims.topSocketOuterLength() 122 | cylinder2 = Part.makeCylinder(r2, h2, aux["p5"]) 123 | outer = cylinder1.fuse([cone, cylinder2]) 124 | return outer 125 | 126 | @classmethod 127 | def createInnerPart(cls, obj): 128 | dims = cls.extractDimensions(obj) 129 | # Create parts which must be removed from the coupling. 130 | if dims.PID() == dims.PID1(): 131 | return cls.createInnerPartEqual(obj) 132 | else: 133 | return cls.createInnerPartReduced(obj) 134 | 135 | @classmethod 136 | def createInnerPartEqual(cls, obj): 137 | """Create the inner part from cylinders. This is when POD and P=D1 are the equal.""" 138 | dims = cls.extractDimensions(obj) 139 | aux = dims.calculateAuxiliararyPoints() 140 | # Create lower inner cylinder. 141 | height1 = dims.socketDepthA5() 142 | cylinder1i = Part.makeCylinder(dims.POD / 2.0, height1) 143 | # Create intermediatiate inner cylinder (from beginning to the end of the complete socket). 144 | height2 = dims.L 145 | cylinder2i = Part.makeCylinder(dims.PID() / 2.0, height2, aux["p1"]) 146 | # Create an upper inner cylinder. 147 | cylinder3i = Part.makeCylinder(dims.POD / 2.0, height1, aux["p3"]) 148 | inner = cylinder1i.fuse([cylinder2i, cylinder3i]) 149 | return inner 150 | 151 | @classmethod 152 | def createInnerPartReduced(cls, obj): 153 | """Create a outer part which is cylinder+cone+cylinder.""" 154 | dims = cls.extractDimensions(obj) 155 | aux = dims.calculateAuxiliararyPoints() 156 | # Create a lower cylinder. 157 | r = dims.POD / 2.0 158 | h = dims.socketDepthA5() 159 | cylinder1i = Part.makeCylinder(r, h, aux["p1"]) 160 | # Create a cone and put it on the cylinder 1. 161 | r1 = dims.PID() / 2.0 162 | r2 = dims.PID1() / 2.0 163 | hc = dims.N 164 | cone = Part.makeCone(r1, r2, hc, aux["p2"]) 165 | # Create an upper cylinder. 166 | r = dims.POD1 / 2 167 | h = dims.socketDepthA5() 168 | cylinder2i = Part.makeCylinder(r, h, aux["p3"]) 169 | inner = cylinder1i.fuse([cone, cylinder2i]) 170 | return inner 171 | 172 | def execute(self, obj): 173 | # Create the shape of the coupling. 174 | inner = Coupling.createInnerPart(obj) 175 | outer = Coupling.createOuterPart(obj) 176 | shape = outer.cut(inner) 177 | obj.Shape = shape 178 | # define Ports, i.e. where the tube have to be placed 179 | obj.Ports = self.getPorts(obj) 180 | 181 | def getPorts(self, obj): 182 | """Calculate coordinates of the ports.""" 183 | dims = self.extractDimensions(obj) 184 | aux = dims.calculateAuxiliararyPoints() 185 | return [aux["p2"], aux["p3"]] 186 | 187 | @classmethod 188 | def getPortRotationAngles(cls, obj): 189 | """Calculate coordinates of the ports rotation and return them as vectorsself. 190 | 191 | x = Yaw 192 | y = Pitch 193 | z = Roll 194 | """ 195 | bottom = FreeCAD.Vector(0, 90, 0) 196 | top = FreeCAD.Vector(0, -90, 0) 197 | 198 | return [bottom, top] 199 | 200 | 201 | class CouplingBuilder: 202 | """Create a coupling using Dodo/Flamingo.""" 203 | 204 | def __init__(self, document): 205 | self.dims = CouplingMod.Dimensions() 206 | self.pos = FreeCAD.Vector(0, 0, 0) # Define default initial position. 207 | self.document = document 208 | 209 | def create(self, obj): 210 | """Create a couzpling. 211 | 212 | Before call it, call 213 | feature = self.document.addObject("Part::FeaturePython","OSE-Coupling") 214 | """ 215 | coupling = Coupling(obj, PSize="", dims=self.dims) 216 | obj.ViewObject.Proxy = 0 217 | obj.Placement.Base = self.pos 218 | 219 | return coupling 220 | 221 | 222 | # Create a test coupling. 223 | def Test(): 224 | document = FreeCAD.activeDocument() 225 | builder = CouplingBuilder(document) 226 | feature = document.addObject("Part::FeaturePython", "OSE-Coupling") 227 | builder.create(feature) 228 | document.recompute() 229 | 230 | # Test() 231 | -------------------------------------------------------------------------------- /Resources/icons/CreateBushing.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 21 | 23 | 26 | 30 | 34 | 35 | 46 | 48 | 52 | 56 | 57 | 68 | 75 | 86 | 97 | 100 | 104 | 108 | 109 | 119 | 130 | 131 | 152 | 159 | 160 | 162 | 163 | 165 | image/svg+xml 166 | 168 | 169 | 170 | [wmayer] 171 | 172 | 173 | Part_Cylinder 174 | 2011-10-10 175 | http://www.freecadweb.org/wiki/index.php?title=Artwork 176 | 177 | 178 | FreeCAD 179 | 180 | 181 | FreeCAD/src/Mod/Part/Gui/Resources/icons/Part_Cylinder.svg 182 | 183 | 184 | FreeCAD LGPL2+ 185 | 186 | 187 | https://www.gnu.org/copyleft/lesser.html 188 | 189 | 190 | [agryson] Alexander Gryson 191 | 192 | 193 | 194 | 195 | 196 | 200 | 206 | 212 | 218 | 223 | 229 | 234 | 240 | 246 | 253 | 254 | 255 | -------------------------------------------------------------------------------- /Resources/icons/CreateTee.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 21 | 23 | 26 | 30 | 34 | 35 | 46 | 48 | 52 | 56 | 57 | 68 | 75 | 86 | 97 | 100 | 104 | 108 | 109 | 119 | 129 | 139 | 149 | 159 | 168 | 179 | 180 | 201 | 208 | 209 | 211 | 212 | 214 | image/svg+xml 215 | 217 | 218 | 219 | [wmayer] 220 | 221 | 222 | 223 | 2011-10-10 224 | http://www.freecadweb.org/wiki/index.php?title=Artwork 225 | 226 | 227 | FreeCAD 228 | 229 | 230 | FreeCAD/src/Mod/Part/Gui/Resources/icons/Part_Cylinder.svg 231 | 232 | 233 | FreeCAD LGPL2+ 234 | 235 | 236 | https://www.gnu.org/copyleft/lesser.html 237 | 238 | 239 | [agryson] Alexander Gryson 240 | 241 | 242 | 243 | 244 | 245 | 249 | 255 | 261 | 268 | 275 | 283 | 289 | 290 | 291 | -------------------------------------------------------------------------------- /doc/adjust-ports.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 29 | 34 | 35 | 44 | 49 | 50 | 58 | 64 | 65 | 66 | 84 | 87 | 88 | 90 | 91 | 93 | image/svg+xml 94 | 96 | 97 | 98 | 99 | 100 | 104 | 111 | 119 | 124 | 129 | 134 | 141 | 148 | 155 | 163 | 171 | 178 | 183 | 188 | x 199 | y 210 | 215 | z 226 | 233 | 240 | 248 | 255 | 256 | 257 | -------------------------------------------------------------------------------- /Resources/icons/MoveAround.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | image/svg+xml 58 | 59 | Draft_Move 60 | 61 | Mon Oct 10 13:44:52 2011 +0000 62 | 63 | 64 | [wmayer] 65 | 66 | 67 | 68 | 69 | FreeCAD LGPL2+ 70 | 71 | 72 | 73 | 74 | FreeCAD 75 | 76 | 77 | FreeCAD/src/Mod/Draft/Resources/icons/Draft_Move.svg 78 | http://www.freecadweb.org/wiki/index.php?title=Artwork 79 | 80 | 81 | [agryson] Alexander Gryson 82 | 83 | 84 | 85 | 86 | arrow 87 | move 88 | arrows 89 | compass 90 | cross 91 | 92 | 93 | Four equally sized arrow heads at 90° to eachother, all joined at the tail 94 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /OsePiping/FlElbow.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Authors: oddtopus, Ruslan Krenzler 3 | # Date: 24 March 2018 4 | # Create a elbow-fitting using Dodo/Flamingo workbench. 5 | 6 | import FreeCAD 7 | import Part 8 | # Parent class from Dodo or Flamingo. 9 | try: 10 | from pFeatures import pypeType 11 | except ModuleNotFoundError: 12 | from pipeFeatures import pypeType 13 | import OsePiping.Elbow as ElbowMod 14 | 15 | 16 | # The value RELATIVE_EPSILON is used to slightly change the size of parts 17 | # to prevent problems with boolean operations. 18 | # Keep this value very small. 19 | # For example, the outer bent part of the elbow dissaperas when it has 20 | # the same radius as the cylinder at the ends. 21 | RELATIVE_EPSILON = 0.000001 22 | 23 | 24 | class Elbow(pypeType): 25 | def __init__(self, obj, PSize="90degBend20x10", BendAngle=90, M=30, POD=20, PThk=10, H=30, J=20): 26 | # run parent __init__ and define common attributes 27 | super(Elbow, self).__init__(obj) 28 | obj.PType = "OSE_Elbow" 29 | obj.PRating = "ElbowFittingFromAnyCatalog" 30 | obj.PSize = PSize # Pipe size 31 | # define specific attributes 32 | obj.addProperty("App::PropertyLength", "M", "Elbow", 33 | "Outer diameter of the elbow.").M = M 34 | obj.addProperty("App::PropertyLength", "POD", "Elbow", 35 | "Pipe Outer Diameter.").POD = POD 36 | obj.addProperty("App::PropertyLength", "PThk", "Elbow", 37 | "Pipe wall thickness").PThk = PThk 38 | obj.addProperty("App::PropertyAngle", "BendAngle", 39 | "Elbow", "Bend Angle.").BendAngle = BendAngle 40 | obj.addProperty("App::PropertyLength", "H", "Elbow", 41 | "Distance between the center and a elbow end").H = H 42 | obj.addProperty("App::PropertyLength", "J", "Elbow", 43 | "Distnace from the center to begin of innerpart of the socket").J = J 44 | 45 | # New Dodo-pypeType already contains Ports, use them. 46 | # But if pypeType does does not have Ports, add them. 47 | if "App::PropertyVectorList" not in obj.supportedProperties(): 48 | obj.addProperty("App::PropertyVectorList", "Ports", "Elbow", "Ports relative positions.") 49 | obj.Ports = self.getPorts(obj) 50 | 51 | obj.addProperty("App::PropertyVectorList", "PortRotationAngles", "Elbow", 52 | "Ports rotation angles.").PortRotationAngles = self.getPortRotationAngles(obj) 53 | obj.addProperty("App::PropertyString", "PartNumber", 54 | "Elbow", "Part number").PartNumber = "" 55 | # Make Ports read only. 56 | obj.setEditorMode("Ports", 1) 57 | obj.setEditorMode("PortRotationAngles", 1) 58 | 59 | def onChanged(self, obj, prop): 60 | # if you aim to do something when an attribute is changed 61 | # place the code here: 62 | # e.g. -> change PSize according the new alpha, PID and POD 63 | 64 | # Dimensions which can change port coordinates. 65 | dim_properties = ["BendAngle", "J"] 66 | if prop in dim_properties: 67 | # This function is called within __init__ too. 68 | # We wait for all dimension. 69 | if set(ElbowMod.DIMENSIONS_USED).issubset(obj.PropertiesList): 70 | obj.Ports = self.getPorts(obj) 71 | # Wait until PortRotationAngles are defined. 72 | if hasattr(obj, "PortRotationAngles"): 73 | obj.PortRotationAngles = self.getPortRotationAngles(obj) 74 | 75 | @staticmethod 76 | def extractDimensions(obj): 77 | dims = ElbowMod.Dimensions() 78 | dims.BendAngle = obj.BendAngle 79 | dims.H = obj.H 80 | dims.J = obj.J 81 | dims.M = obj.M 82 | dims.POD = obj.POD 83 | dims.PThk = obj.PThk 84 | return dims 85 | 86 | @staticmethod 87 | def createBentCylinderDoesNotWork(obj, rCirc): 88 | """Create a cylinder of radius rCirc in x-y plane which is bent in the middle and is streight in the ends. 89 | 90 | The little streight part is necessary, because otherwise the part is not displayed 91 | correctly after performing a boolean operations. Thus we need some overlapping 92 | between bent part and the socket. 93 | 94 | :param group: Group where to add created objects. 95 | :param rCirc: Radius of the cylinder. 96 | 97 | See documentation picture elbow-cacluations.png 98 | """ 99 | # Convert alpha to degree value 100 | dims = Elbow.extractDimensions(obj) 101 | 102 | aux = dims.calculateAuxiliararyPoints() 103 | 104 | alpha = float(dims.BendAngle.getValueAs("deg")) 105 | rBend = dims.M / 2.0 106 | 107 | # Put a base on the streight part. 108 | base = Part.makeCircle(rCirc, aux["p5"], aux["p5"]) 109 | 110 | # Add trajectory 111 | line1 = Part.makeLine(aux["p5"], aux["p2"]) 112 | arc = Part.makeCircle( 113 | rBend, aux["p3"], FreeCAD.Vector(0, 0, 1), 225 - alpha / 2, 225 + alpha / 2) 114 | line2 = Part.makeLine(aux["p4"], aux["p6"]) 115 | 116 | trajectory = Part.Shape([line1, arc, line2]) 117 | # Show trajectory for debugging. 118 | # W Part.Wire([line1, arc, line2]) 119 | # Part.show(W) 120 | # Add a cap (circle, at the other end of the bent cylinder). 121 | cap = Part.makeCircle(rCirc, aux["p5"], aux["p5"]) 122 | # Sweep the circle along the trajectory. 123 | sweep = Part.makeSweepSurface(trajectory, base) # Does not work 124 | sweep = Part.makeSweepSurface(W, base) # Does not work. 125 | # The sweep is only a 2D service consisting of walls only. 126 | # Add circles on both ends of this wall. 127 | end1 = Part.Face(Part.Wire(base)) 128 | # Create other end. 129 | end2 = Part.Face(Part.Wire(cap)) 130 | solid = Part.Solid(Part.Shell([end1, sweep, end2])) 131 | return solid 132 | 133 | @staticmethod 134 | def createBentCylinder(obj, rCirc): 135 | """Create a cylinder of radius rCirc in x-y plane which is bent in the middle. 136 | 137 | :param group: Group where to add created objects. 138 | :param rCirc: Radius of the cylinder. 139 | 140 | See documentation picture elbow-cacluations.png 141 | """ 142 | # Convert alpha to degree value 143 | dims = Elbow.extractDimensions(obj) 144 | 145 | aux = dims.calculateAuxiliararyPoints() 146 | 147 | alpha = float(dims.BendAngle.getValueAs("deg")) 148 | rBend = dims.M / 2.0 149 | 150 | # Put a base on the streight part. 151 | base = Part.makeCircle(rCirc, aux["p2"], aux["p2"]) 152 | 153 | # Add trajectory 154 | trajectory = Part.makeCircle( 155 | rBend, aux["p3"], FreeCAD.Vector(0, 0, 1), 225 - alpha / 2, 225 + alpha / 2) 156 | # Show trajectory for debugging. 157 | # W = W1.fuse([trajectory.Edges]) 158 | # Part.Show(W) 159 | # Add a cap (circle, at the other end of the bent cylinder). 160 | cap = Part.makeCircle(rCirc, aux["p4"], aux["p4"]) 161 | # Sweep the circle along the trajectory. 162 | sweep = Part.makeSweepSurface(trajectory, base) 163 | # The sweep is only a 2D service consisting of walls only. 164 | # Add circles on both ends of this wall. 165 | end1 = Part.Face(Part.Wire(base)) 166 | # Create other end. 167 | end2 = Part.Face(Part.Wire(cap)) 168 | solid = Part.Solid(Part.Shell([end1, sweep, end2])) 169 | return solid 170 | 171 | @staticmethod 172 | def createOuterPart(obj): 173 | dims = Elbow.extractDimensions(obj) 174 | aux = dims.calculateAuxiliararyPoints() 175 | r = dims.M / 2 176 | # For unknow reasons, witoutm the factor r*0.999999 the middle part disappears. 177 | bentPart = Elbow.createBentCylinder(obj, r * (1 + RELATIVE_EPSILON)) 178 | # Create socket along the z axis. 179 | h = float(dims.H) - aux["p2"].Length 180 | socket1 = Part.makeCylinder(r, h, aux["p2"], aux["p2"]) 181 | # Create socket along the bent part. 182 | socket2 = Part.makeCylinder(r, h, aux["p4"], aux["p4"]) 183 | 184 | outer = bentPart.fuse([socket1, socket2]) 185 | return outer 186 | 187 | @staticmethod 188 | def createInnerPart(obj): 189 | dims = Elbow.extractDimensions(obj) 190 | aux = dims.calculateAuxiliararyPoints() 191 | 192 | r = dims.POD / 2 - dims.PThk 193 | 194 | bentPart = Elbow.createBentCylinder(obj, r * (1 + RELATIVE_EPSILON)) 195 | # Create a channel along the z axis. It is longer then necessaryself. 196 | # But it possible can prevent problems with boolean operations. 197 | h = float(dims.H) 198 | chan1 = Part.makeCylinder(r, h, aux["p2"], aux["p2"]) 199 | # Create a channel along the bent part. 200 | chan2 = Part.makeCylinder(r, h, aux["p4"], aux["p4"]) 201 | # Create corresponding socktes. 202 | 203 | rSocket = dims.POD / 2 204 | # The socket length is actually dims.H - dims.J. But we do it longer 205 | # to prevent problems with bulean operations 206 | hSocket = dims.H 207 | socket1 = Part.makeCylinder(rSocket, hSocket, aux["p5"], aux["p5"]) 208 | socket2 = Part.makeCylinder(rSocket, hSocket, aux["p6"], aux["p6"]) 209 | 210 | inner = bentPart.fuse([chan1, chan2, socket1, socket2]) 211 | return inner 212 | 213 | @staticmethod 214 | def createShape(obj): 215 | outer = Elbow.createOuterPart(obj) 216 | inner = Elbow.createInnerPart(obj) 217 | return outer.cut(inner) 218 | 219 | def execute(self, obj): 220 | # Create the shape of the elbow. 221 | shape = Elbow.createShape(obj) 222 | obj.Shape = shape 223 | # Recalculate ports. 224 | obj.Ports = self.getPorts(obj) 225 | 226 | def getPorts(self, obj): 227 | dims = Elbow.extractDimensions(obj) 228 | aux = dims.calculateAuxiliararyPoints() 229 | # FreeCAD.Console.PrintMessage("Ports are %s and %s"%(aux["p5"], aux["p6"])) 230 | return [aux["p5"], aux["p6"]] 231 | 232 | def getPortRotationAngles(self, obj): 233 | """Calculate coordinates of the ports rotation and return them as vectorsself. 234 | 235 | x = Yaw 236 | y = Pitch 237 | z = Roll 238 | """ 239 | dims = Elbow.extractDimensions(obj) 240 | half = dims.BendAngle / 2 241 | # -45° and 135° are rotation of 0° elbow. They acts as a refence for a bent elbow. 242 | end0 = FreeCAD.Vector(-45 + half.Value, 0, 0) 243 | end1 = FreeCAD.Vector(135 - half.Value, 0, 0) 244 | return [end0, end1] 245 | 246 | 247 | class ElbowBuilder: 248 | """Create elbow using Dodo/Flamingo.""" 249 | 250 | def __init__(self, document): 251 | self.dims = ElbowMod.Dimensions() 252 | self.pos = FreeCAD.Vector(0, 0, 0) # Define default initial position. 253 | self.document = document 254 | 255 | def create(self, obj): 256 | """Create an elbow.""" 257 | elbow = Elbow(obj, PSize="", BendAngle=self.dims.BendAngle, M=self.dims.M, POD=self.dims.POD, 258 | PThk=self.dims.PThk, H=self.dims.H, J=self.dims.J) 259 | obj.ViewObject.Proxy = 0 260 | obj.Placement.Base = self.pos 261 | 262 | return elbow 263 | 264 | 265 | # Test builder. 266 | def TestElbow(): 267 | document = FreeCAD.activeDocument() 268 | builder = ElbowBuilder(document) 269 | feature = document.addObject("Part::FeaturePython", "OSE-Elbow") 270 | # builder.dims.BendAngle = FreeCAD.Units.parseQuantity("90 deg") 271 | builder.create(feature) 272 | document.recompute() 273 | 274 | # TestElbow() 275 | --------------------------------------------------------------------------------