├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── MANIFEST.in ├── README.md ├── docs ├── commands.md ├── images │ ├── 00-create-sketch.png │ ├── 01-select-orientation.png │ ├── 02-create-frame-skeleton.png │ ├── 10-profiles-task-2.png │ ├── 10-profiles-task.png │ ├── 10-profiles.png │ ├── 11-profiles-family.png │ ├── 13-edge-selection.png │ ├── 14-profiles-done.png │ ├── 14-zoom-on-profiles.png │ ├── 20-sketch-base-placement-2.png │ ├── 20-sketch-base-placement.png │ ├── 21-stacked-frames.png │ ├── 22-hide-profiles.png │ ├── 23-select-vertexes.png │ ├── 24-create-parametric-line.png │ ├── 25-parametric-line.png │ ├── 26-cube-done.png │ ├── 30-mapmode-sketch.png │ ├── 31-mapmode.png │ ├── 32-mapmode-dialog.png │ ├── 33-mapmode.png │ ├── 40-show-first-frame.png │ ├── 41-bevels.png │ ├── 42-batchs-bevels.png │ ├── 50-base-config.png │ ├── 51-add-offset.png │ ├── 52-select-touching-profiles.png │ ├── 53-create-miter-end.png │ ├── 54-miter-end.png │ ├── 60-startwith.png │ ├── 61-bad-joint.png │ ├── 62-endtrim-task.png │ ├── 62-endtrim.png │ ├── 63-select-trimmed-body-1.png │ ├── 63-select-trimmed-body-2.png │ ├── 64-select-cuttype-1.png │ ├── 64-select-cuttype-2.png │ ├── 64-select-trimming-boundaries-1.png │ ├── 64-select-trimming-boundaries-2.png │ ├── 70-part-container.png │ ├── 71-part-container.png │ ├── 72-fusion-done.png │ ├── 72-fusion.png │ ├── 80-body.png │ ├── 81-basefeature.png │ ├── 82-making-holes.png │ └── 83-holes-made.png └── tutorial.md ├── freecad └── frameforge │ ├── __init__.py │ ├── _utils.py │ ├── create_end_miter_tool.py │ ├── create_profiles_tool.py │ ├── create_trimmed_profiles_tool.py │ ├── init_gui.py │ ├── parametric_line.py │ ├── profile.py │ ├── resources │ ├── icons │ │ ├── box.svg │ │ ├── corner-coped-type.svg │ │ ├── corner-end-miter.svg │ │ ├── corner-end-trim.svg │ │ ├── corner-simple-type.svg │ │ ├── corner.svg │ │ ├── discretize.svg │ │ ├── extendcurve.svg │ │ ├── joincurve.svg │ │ ├── line.svg │ │ ├── list-add.svg │ │ ├── list-remove.svg │ │ ├── metalwb.svg │ │ ├── overlap.svg │ │ ├── parts_list.svg │ │ ├── splitcurve.svg │ │ ├── trim_extend.svg │ │ └── warehouse_profiles.svg │ ├── images │ │ └── profiles │ │ │ ├── Metal │ │ │ ├── Equal_Leg_Angles.png │ │ │ ├── Equal_Leg_Angles_Fillet.png │ │ │ ├── Flat_Sections.png │ │ │ ├── HEA.png │ │ │ ├── HEA_Fillet.png │ │ │ ├── HEB.png │ │ │ ├── HEB_Fillet.png │ │ │ ├── HEM.png │ │ │ ├── HEM_Fillet.png │ │ │ ├── IPE.png │ │ │ ├── IPE_Fillet.png │ │ │ ├── IPN.png │ │ │ ├── IPN_Fillet.png │ │ │ ├── Pipe.png │ │ │ ├── Rectangular_Hollow.png │ │ │ ├── Rectangular_Hollow_Fillet.png │ │ │ ├── Round_Bar.png │ │ │ ├── Square.png │ │ │ ├── Square_Fillet.png │ │ │ ├── Square_Hollow.png │ │ │ ├── Square_Hollow_Fillet.png │ │ │ ├── UPE.png │ │ │ ├── UPE_Fillet.png │ │ │ ├── UPN.png │ │ │ ├── UPN_Fillet.png │ │ │ ├── Unequal_Leg_Angles.png │ │ │ └── Unequal_Leg_Angles_Fillet.png │ │ │ └── Warehouse.png │ ├── profiles │ │ └── metal.json │ └── ui │ │ ├── create_profiles.ui │ │ └── create_trimmed_profiles.ui │ ├── translate_utils.py │ ├── trimmed_profile.py │ └── version.py ├── package.xml ├── prof_extractor.py └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | **.pyc 2 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(Namespace_SRCS 2 | freecad/frameforge/resources/icons/corner.svg 3 | freecad/frameforge/resources/icons/splitcurve.svg 4 | freecad/frameforge/resources/icons/warehouse_profiles.svg 5 | freecad/frameforge/resources/icons/trim_extend.svg 6 | freecad/frameforge/resources/icons/metalwb.svg 7 | freecad/frameforge/resources/icons/line.svg 8 | freecad/frameforge/resources/icons/corner-end-miter.svg 9 | freecad/frameforge/resources/icons/discretize.svg 10 | freecad/frameforge/resources/icons/joincurve.svg 11 | freecad/frameforge/resources/icons/overlap.svg 12 | freecad/frameforge/resources/icons/box.svg 13 | freecad/frameforge/resources/icons/parts_list.svg 14 | freecad/frameforge/resources/icons/corner-coped-type.svg 15 | freecad/frameforge/resources/icons/corner-simple-type.svg 16 | freecad/frameforge/resources/icons/corner-end-trim.svg 17 | freecad/frameforge/resources/icons/list-remove.svg 18 | freecad/frameforge/resources/icons/extendcurve.svg 19 | freecad/frameforge/resources/icons/list-add.svg 20 | 21 | freecad/frameforge/resources/images/profiles/Metal/HEB_Fillet.png 22 | freecad/frameforge/resources/images/profiles/Metal/Square_Hollow.png 23 | freecad/frameforge/resources/images/profiles/Metal/HEM_Fillet.png 24 | freecad/frameforge/resources/images/profiles/Metal/UPN_Fillet.png 25 | freecad/frameforge/resources/images/profiles/Metal/Round_Bar.png 26 | freecad/frameforge/resources/images/profiles/Metal/UPE.png 27 | freecad/frameforge/resources/images/profiles/Metal/HEA.png 28 | freecad/frameforge/resources/images/profiles/Metal/HEA_Fillet.png 29 | freecad/frameforge/resources/images/profiles/Metal/HEB.png 30 | freecad/frameforge/resources/images/profiles/Metal/IPN_Fillet.png 31 | freecad/frameforge/resources/images/profiles/Metal/IPN.png 32 | freecad/frameforge/resources/images/profiles/Metal/UPN.png 33 | freecad/frameforge/resources/images/profiles/Metal/Square_Fillet.png 34 | freecad/frameforge/resources/images/profiles/Metal/Unequal_Leg_Angles.png 35 | freecad/frameforge/resources/images/profiles/Metal/Flat_Sections.png 36 | freecad/frameforge/resources/images/profiles/Metal/IPE.png 37 | freecad/frameforge/resources/images/profiles/Metal/UPE_Fillet.png 38 | freecad/frameforge/resources/images/profiles/Metal/Unequal_Leg_Angles_Fillet.png 39 | freecad/frameforge/resources/images/profiles/Metal/Rectangular_Hollow_Fillet.png 40 | freecad/frameforge/resources/images/profiles/Metal/HEM.png 41 | freecad/frameforge/resources/images/profiles/Metal/Square_Hollow_Fillet.png 42 | freecad/frameforge/resources/images/profiles/Metal/Equal_Leg_Angles_Fillet.png 43 | freecad/frameforge/resources/images/profiles/Metal/Equal_Leg_Angles.png 44 | freecad/frameforge/resources/images/profiles/Metal/Rectangular_Hollow.png 45 | freecad/frameforge/resources/images/profiles/Metal/IPE_Fillet.png 46 | freecad/frameforge/resources/images/profiles/Metal/Pipe.png 47 | freecad/frameforge/resources/images/profiles/Metal/Square.png 48 | freecad/frameforge/resources/images/profiles/Warehouse.png 49 | 50 | freecad/frameforge/resources/ui/create_trimmed_profiles.ui 51 | freecad/frameforge/resources/ui/create_profiles.ui 52 | 53 | freecad/frameforge/resources/profiles/metal.json 54 | 55 | freecad/frameforge/version.py 56 | freecad/frameforge/translate_utils.py 57 | freecad/frameforge/create_trimmed_profiles_tool.py 58 | freecad/frameforge/profile.py 59 | freecad/frameforge/_utils.py 60 | freecad/frameforge/parametric_line.py 61 | freecad/frameforge/__init__.py 62 | freecad/frameforge/create_end_miter_tool.py 63 | freecad/frameforge/trimmed_profile.py 64 | freecad/frameforge/init_gui.py 65 | freecad/frameforge/create_profiles_tool.py 66 | ) 67 | 68 | SOURCE_GROUP("" FILES ${Namespace_SRCS}) 69 | 70 | ADD_CUSTOM_TARGET(SEARCHBAR ALL 71 | SOURCES ${Namespace_SRCS} 72 | ) 73 | 74 | fc_copy_sources(SEARCHBAR "${CMAKE_BINARY_DIR}/Mod/FrameForge" ${Namespace_SRCS}) 75 | 76 | INSTALL( 77 | FILES 78 | ${Namespace_SRCS} 79 | DESTINATION 80 | Mod/SearchBar 81 | ) 82 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | recursive-include freecad/frameforge/resources * 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FrameForge 2 | 3 | Frameforge is a FreeCAD workbench that aids in designing beams and frames, with cut, mitter joins and so on. 4 | 5 | ## Prerequisite 6 | 7 | - FreeCAD ≥ v0.21.x 8 | 9 | ## Installation 10 | 11 | Frameforge workbench can be installed via the [Addon Manager](https://wiki.freecad.org/Std_AddonMgr) 12 | 13 | ## Quick Start 14 | 15 | ### Create the skeleton 16 | 17 | Beams are mapped onto Edges or ParametricLine (from a Sketch for instance) 18 | 19 | For a start, we are going to create a simple frame. 20 | 21 | 1. In a new file, switch to the Frameforge workbench. 22 | 2. Create a [sketch](https://wiki.freecad.org/Sketcher_NewSketch) 23 | 3. A dialog will open asking you to 'Select orientation'. Choose the XY for instance. 24 | 3. Draw a simple square in the sketch... this will be our skeleton 25 | 26 | ![Create Skeleton](docs/images/02-create-frame-skeleton.png) 27 | 28 | 5. Close the Sketch edit mode. 29 | 30 | ### Create the frame 31 | 32 | 1. Launch the FrameForge Profile tool 33 | 34 | ![profile](docs/images/10-profiles.png) 35 | 36 | 2. It opens a FrameForge dialog (with options) 37 | 38 | ![profile](docs/images/10-profiles-task.png) 39 | ![profile](docs/images/10-profiles-task-2.png) 40 | 41 | 3. Select a profile from the lists (Material / Family / Size). 42 | *Note*: Size can be adjusted just below the family, the tool has a lot of predefined profiles. Same for parameters. 43 | 44 | ![profiles choice](docs/images/11-profiles-family.png) 45 | 46 | 4. In the 3D View, select edges to apply the profile creation: 47 | 48 | ![Edge Selection](docs/images/13-edge-selection.png) 49 | 50 | 5. Press OK in the Create Profile Task. 51 | **Result**: now there are four profiles! 52 | 53 | ![Profiles](docs/images/14-profiles-done.png) 54 | 55 | ![Zoom in profile](docs/images/14-zoom-on-profiles.png) 56 | 57 | **Voila!** You have your first frame! For more information, follow the [tutorial](docs/tutorial.md) 58 | 59 | ## Maintainer 60 | 61 | Vivien HENRY 62 | vivien.henry@inductivebrain.fr 63 | 64 | 65 | ## Credits 66 | 67 | This workbench is based on [MetalWB](https://framagit.org/Veloma/freecad_metal_workbench) 68 | 69 | Special thanks to: 70 | 71 | - Vincent B 72 | - Quentin Plisson 73 | - rockn 74 | - Jonathan Wiedemann 75 | 76 | And others people that I don't know but they should be in this [thread](https://forum.freecad.org/viewtopic.php?style=5&t=72389) 77 | 78 | 79 | ## Changelog 80 | 81 | * v0.1.3 82 | - Fix #10, Non attached profile (move profile inside the sketch 's parent) 83 | - Fix #27, Link to object go out of the allowed scope 84 | - Implement #23 Allow profile creation with selection of a whole sketch 85 | - Allows to create a Part to group all the Profile 86 | - Profile Naming Option 87 | 88 | * v0.1.2 89 | - Fix recursive import 90 | * v0.1.1 91 | - remove f-string with quote and double quote 92 | * v0.1.0 93 | - Porting code from MetalWB 94 | - Improving UI 95 | - Split Corners into EndTrim and EndMiter 96 | 97 | 98 | ## LICENSE 99 | 100 | FrameForge is licensed under the [GPLv3](LICENSE) 101 | -------------------------------------------------------------------------------- /docs/commands.md: -------------------------------------------------------------------------------- 1 | # Code Snippets 2 | There is no way to add a button, menu entry from python to a workbench which is added with c++. So here is a comparison how to do that with python and with c++. 3 | 4 | ## Adding a command: 5 | This can be done either with python or c++. 6 | 7 | ### 1. python 8 | 9 | ```python 10 | import FreeCAD as App 11 | 12 | class MyCommand(object): 13 | def IsActive(self): 14 | """ 15 | availability of the command (eg.: check for existence of a document,...) 16 | if this function returns False, the menu/ buttons are ßdisabled (gray) 17 | """ 18 | if App.ActiveDocument is None: 19 | return False 20 | else: 21 | return True 22 | 23 | def GetResources(self): 24 | """ 25 | resources which are used by buttons and menu-items 26 | """ 27 | return {'Pixmap': 'path_to_icon.svg', 'MenuText': 'my command', 'ToolTip': 'very short description'} 28 | 29 | def Activated(self): 30 | """ 31 | the function to be handled, when a user starts the command 32 | """ 33 | ``` 34 | To register the command in FreeCAD: 35 | 36 | ```python 37 | import FreeCADGui as Gui 38 | Gui.addCommand('MyCommand', MyCommand()) 39 | ``` 40 | 41 | Adding a new toolbar/menu: 42 | ```python 43 | from FreeCADGui import Workbench 44 | class myWorkbench(Workbench): 45 | MenuText = "name_of_workbench" 46 | ToolTip = "short description of workbench" 47 | Icon = "path_to_icon.svg" 48 | 49 | def GetClassName(self): 50 | return "Gui::PythonWorkbench" 51 | 52 | def Initialize(self): 53 | self.appendToolbar("Gear", ["MyCommand"]) 54 | self.appendMenu("Gear", ["MyCommand"]) 55 | ``` 56 | 57 | ### 2. C++ 58 | 59 | ```c++ 60 | 61 | #include 62 | #include 63 | #include 64 | #include 65 | 66 | using namespace std; 67 | 68 | DEF_STD_CMD_A(MyCommand) 69 | 70 | MyCommand::MyCommand() 71 | : Command("MyCommand") 72 | { 73 | sAppModule = "module"; 74 | sGroup = QT_TR_NOOP("Mesh"); 75 | sMenuText = QT_TR_NOOP("my command"); 76 | sToolTipText = QT_TR_NOOP("very short description"); 77 | sWhatsThis = "MyCommand"; 78 | sStatusTip = sToolTipText; 79 | } 80 | 81 | void MyCommand::activated(int) 82 | { 83 | // the function to be handled, when a user starts the command 84 | } 85 | 86 | bool MyCommand::isActive(void) 87 | { 88 | // availability of the command (eg.: check for existence of a document,...) 89 | // if this function returns False, the menu/ buttons are ßdisabled (gray) 90 | return (hasActiveDocument() && !Gui::Control().activeDialog()); 91 | } 92 | ``` 93 | To register the command in FreeCAD: 94 | 95 | ```c++ 96 | #include 97 | 98 | Gui::CommandManager &rcCmdMgr = Gui::Application::Instance->commandManager(); 99 | rcCmdMgr.addCommand(new MyCommand()); 100 | ``` 101 | Adding a item to a menu/toolbar: 102 | 103 | if your command is added with python you have to run this code: 104 | in src/module/Gui/AppModuleGui.cpp add to PyMOD_INIT_FUNC: 105 | 106 | ```c++ 107 | // try to instantiate a python module 108 | try{ 109 | Base::Interpreter().runStringObject("import MyCommands"); 110 | } catch (Base::PyException &err){ 111 | err.ReportException(); 112 | } 113 | ``` 114 | 115 | and add the name of the command to a tooltip/menu in src/module/Gui/Workbench.cpp Workbench::setupToolBars 116 | 117 | 118 | ```c++ 119 | Gui::ToolBarItem* Workbench::setupToolBars() const 120 | { 121 | Gui::ToolBarItem* root = StdWorkbench::setupToolBars(); 122 | Gui::ToolBarItem* myToolbar = new Gui::ToolBarItem(root); 123 | myToolbar->setCommand("my_commands"); 124 | *myToolbar << "MyCommand"; 125 | return root; 126 | } 127 | ``` 128 | -------------------------------------------------------------------------------- /docs/images/00-create-sketch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukh/frameforge/c85108171a1b728e61d780cced3423aff41c6fa5/docs/images/00-create-sketch.png -------------------------------------------------------------------------------- /docs/images/01-select-orientation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukh/frameforge/c85108171a1b728e61d780cced3423aff41c6fa5/docs/images/01-select-orientation.png -------------------------------------------------------------------------------- /docs/images/02-create-frame-skeleton.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukh/frameforge/c85108171a1b728e61d780cced3423aff41c6fa5/docs/images/02-create-frame-skeleton.png -------------------------------------------------------------------------------- /docs/images/10-profiles-task-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukh/frameforge/c85108171a1b728e61d780cced3423aff41c6fa5/docs/images/10-profiles-task-2.png -------------------------------------------------------------------------------- /docs/images/10-profiles-task.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukh/frameforge/c85108171a1b728e61d780cced3423aff41c6fa5/docs/images/10-profiles-task.png -------------------------------------------------------------------------------- /docs/images/10-profiles.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukh/frameforge/c85108171a1b728e61d780cced3423aff41c6fa5/docs/images/10-profiles.png -------------------------------------------------------------------------------- /docs/images/11-profiles-family.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukh/frameforge/c85108171a1b728e61d780cced3423aff41c6fa5/docs/images/11-profiles-family.png -------------------------------------------------------------------------------- /docs/images/13-edge-selection.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukh/frameforge/c85108171a1b728e61d780cced3423aff41c6fa5/docs/images/13-edge-selection.png -------------------------------------------------------------------------------- /docs/images/14-profiles-done.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukh/frameforge/c85108171a1b728e61d780cced3423aff41c6fa5/docs/images/14-profiles-done.png -------------------------------------------------------------------------------- /docs/images/14-zoom-on-profiles.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukh/frameforge/c85108171a1b728e61d780cced3423aff41c6fa5/docs/images/14-zoom-on-profiles.png -------------------------------------------------------------------------------- /docs/images/20-sketch-base-placement-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukh/frameforge/c85108171a1b728e61d780cced3423aff41c6fa5/docs/images/20-sketch-base-placement-2.png -------------------------------------------------------------------------------- /docs/images/20-sketch-base-placement.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukh/frameforge/c85108171a1b728e61d780cced3423aff41c6fa5/docs/images/20-sketch-base-placement.png -------------------------------------------------------------------------------- /docs/images/21-stacked-frames.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukh/frameforge/c85108171a1b728e61d780cced3423aff41c6fa5/docs/images/21-stacked-frames.png -------------------------------------------------------------------------------- /docs/images/22-hide-profiles.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukh/frameforge/c85108171a1b728e61d780cced3423aff41c6fa5/docs/images/22-hide-profiles.png -------------------------------------------------------------------------------- /docs/images/23-select-vertexes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukh/frameforge/c85108171a1b728e61d780cced3423aff41c6fa5/docs/images/23-select-vertexes.png -------------------------------------------------------------------------------- /docs/images/24-create-parametric-line.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukh/frameforge/c85108171a1b728e61d780cced3423aff41c6fa5/docs/images/24-create-parametric-line.png -------------------------------------------------------------------------------- /docs/images/25-parametric-line.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukh/frameforge/c85108171a1b728e61d780cced3423aff41c6fa5/docs/images/25-parametric-line.png -------------------------------------------------------------------------------- /docs/images/26-cube-done.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukh/frameforge/c85108171a1b728e61d780cced3423aff41c6fa5/docs/images/26-cube-done.png -------------------------------------------------------------------------------- /docs/images/30-mapmode-sketch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukh/frameforge/c85108171a1b728e61d780cced3423aff41c6fa5/docs/images/30-mapmode-sketch.png -------------------------------------------------------------------------------- /docs/images/31-mapmode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukh/frameforge/c85108171a1b728e61d780cced3423aff41c6fa5/docs/images/31-mapmode.png -------------------------------------------------------------------------------- /docs/images/32-mapmode-dialog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukh/frameforge/c85108171a1b728e61d780cced3423aff41c6fa5/docs/images/32-mapmode-dialog.png -------------------------------------------------------------------------------- /docs/images/33-mapmode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukh/frameforge/c85108171a1b728e61d780cced3423aff41c6fa5/docs/images/33-mapmode.png -------------------------------------------------------------------------------- /docs/images/40-show-first-frame.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukh/frameforge/c85108171a1b728e61d780cced3423aff41c6fa5/docs/images/40-show-first-frame.png -------------------------------------------------------------------------------- /docs/images/41-bevels.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukh/frameforge/c85108171a1b728e61d780cced3423aff41c6fa5/docs/images/41-bevels.png -------------------------------------------------------------------------------- /docs/images/42-batchs-bevels.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukh/frameforge/c85108171a1b728e61d780cced3423aff41c6fa5/docs/images/42-batchs-bevels.png -------------------------------------------------------------------------------- /docs/images/50-base-config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukh/frameforge/c85108171a1b728e61d780cced3423aff41c6fa5/docs/images/50-base-config.png -------------------------------------------------------------------------------- /docs/images/51-add-offset.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukh/frameforge/c85108171a1b728e61d780cced3423aff41c6fa5/docs/images/51-add-offset.png -------------------------------------------------------------------------------- /docs/images/52-select-touching-profiles.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukh/frameforge/c85108171a1b728e61d780cced3423aff41c6fa5/docs/images/52-select-touching-profiles.png -------------------------------------------------------------------------------- /docs/images/53-create-miter-end.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukh/frameforge/c85108171a1b728e61d780cced3423aff41c6fa5/docs/images/53-create-miter-end.png -------------------------------------------------------------------------------- /docs/images/54-miter-end.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukh/frameforge/c85108171a1b728e61d780cced3423aff41c6fa5/docs/images/54-miter-end.png -------------------------------------------------------------------------------- /docs/images/60-startwith.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukh/frameforge/c85108171a1b728e61d780cced3423aff41c6fa5/docs/images/60-startwith.png -------------------------------------------------------------------------------- /docs/images/61-bad-joint.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukh/frameforge/c85108171a1b728e61d780cced3423aff41c6fa5/docs/images/61-bad-joint.png -------------------------------------------------------------------------------- /docs/images/62-endtrim-task.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukh/frameforge/c85108171a1b728e61d780cced3423aff41c6fa5/docs/images/62-endtrim-task.png -------------------------------------------------------------------------------- /docs/images/62-endtrim.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukh/frameforge/c85108171a1b728e61d780cced3423aff41c6fa5/docs/images/62-endtrim.png -------------------------------------------------------------------------------- /docs/images/63-select-trimmed-body-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukh/frameforge/c85108171a1b728e61d780cced3423aff41c6fa5/docs/images/63-select-trimmed-body-1.png -------------------------------------------------------------------------------- /docs/images/63-select-trimmed-body-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukh/frameforge/c85108171a1b728e61d780cced3423aff41c6fa5/docs/images/63-select-trimmed-body-2.png -------------------------------------------------------------------------------- /docs/images/64-select-cuttype-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukh/frameforge/c85108171a1b728e61d780cced3423aff41c6fa5/docs/images/64-select-cuttype-1.png -------------------------------------------------------------------------------- /docs/images/64-select-cuttype-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukh/frameforge/c85108171a1b728e61d780cced3423aff41c6fa5/docs/images/64-select-cuttype-2.png -------------------------------------------------------------------------------- /docs/images/64-select-trimming-boundaries-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukh/frameforge/c85108171a1b728e61d780cced3423aff41c6fa5/docs/images/64-select-trimming-boundaries-1.png -------------------------------------------------------------------------------- /docs/images/64-select-trimming-boundaries-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukh/frameforge/c85108171a1b728e61d780cced3423aff41c6fa5/docs/images/64-select-trimming-boundaries-2.png -------------------------------------------------------------------------------- /docs/images/70-part-container.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukh/frameforge/c85108171a1b728e61d780cced3423aff41c6fa5/docs/images/70-part-container.png -------------------------------------------------------------------------------- /docs/images/71-part-container.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukh/frameforge/c85108171a1b728e61d780cced3423aff41c6fa5/docs/images/71-part-container.png -------------------------------------------------------------------------------- /docs/images/72-fusion-done.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukh/frameforge/c85108171a1b728e61d780cced3423aff41c6fa5/docs/images/72-fusion-done.png -------------------------------------------------------------------------------- /docs/images/72-fusion.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukh/frameforge/c85108171a1b728e61d780cced3423aff41c6fa5/docs/images/72-fusion.png -------------------------------------------------------------------------------- /docs/images/80-body.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukh/frameforge/c85108171a1b728e61d780cced3423aff41c6fa5/docs/images/80-body.png -------------------------------------------------------------------------------- /docs/images/81-basefeature.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukh/frameforge/c85108171a1b728e61d780cced3423aff41c6fa5/docs/images/81-basefeature.png -------------------------------------------------------------------------------- /docs/images/82-making-holes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukh/frameforge/c85108171a1b728e61d780cced3423aff41c6fa5/docs/images/82-making-holes.png -------------------------------------------------------------------------------- /docs/images/83-holes-made.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukh/frameforge/c85108171a1b728e61d780cced3423aff41c6fa5/docs/images/83-holes-made.png -------------------------------------------------------------------------------- /docs/tutorial.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # FrameForge Tutorial 4 | 5 | FrameForge is dedicated for creating Frames and Beams, and apply operations (miter cuts, trim cuts) on these profiles. 6 | 7 | ## Create the skeleton 8 | 9 | Beams are mapped onto Edges or ParametricLine (from a Sketch for instance) 10 | 11 | For a start, we are going to create a simple frame. 12 | 13 | 1. In a new file, switch to the Frameforge workbench. 14 | 15 | 2. Create a sketch, and select orientation (XY for instance) 16 | 17 | ![Create Sketch](images/00-create-sketch.png) 18 | 19 | ![Select Orientation](images/01-select-orientation.png) 20 | 21 | 22 | 3. Draw a simple square in the sketch... it will be our skeleton 23 | 24 | ![Create Skeleton](images/02-create-frame-skeleton.png) 25 | 26 | 4. Close the Sketch edit mode. 27 | 28 | ## Create the frame 29 | 30 | 1. Launch the Profile tool. 31 | 32 | ![profile](images/10-profiles.png) 33 | 34 | ![profile](images/10-profiles-task.png) 35 | ![profile](images/10-profiles-task-2.png) 36 | 37 | 1. Select a profile from the lists (Material / Family / Size) 38 | 39 | ![profiles choice](images/11-profiles-family.png) 40 | 41 | 42 | You can change the size just below the family, the tool has a lot of predefined profile, you can also change the parameters... 43 | 44 | 45 | 3. In the 3D View, select edges to apply the profile creation: 46 | 47 | ![Edge Selection](images/13-edge-selection.png) 48 | 49 | 1. And press OK in the Create Profile Task. Now, you have four profiles ! 50 | 51 | ![Profiles](images/14-profiles-done.png) 52 | 53 | ![Zoom in profile](images/14-zoom-on-profiles.png) 54 | 55 | 56 | 57 | **And voila ! You have your first frame !** 58 | 59 | 60 | ## Going 3D... Making a cube ! 61 | 62 | We can build more complexe shapes, and there are severals ways of doing it. 63 | 64 | ### More Sketches ! 65 | 66 | We can add more sketches into our project: 67 | 68 | 1. Create a new Sketch 69 | 2. Select the same orientation as the previous one (XY) 70 | 3. Draw a square the same size and placement as the previous one. 71 | 72 | 73 | 4. Now, change the position of the sketch: 74 | 75 | ![Base Placement](images/20-sketch-base-placement.png) 76 | 77 | ![Sketch moved !](images/20-sketch-base-placement-2.png) 78 | 79 | And the new sketch is 400mm on top of the first one ! 80 | 81 | You can therefore use Create Profile Command again to create another square frame ! 82 | 83 | ![Stacked Frames](images/21-stacked-frames.png) 84 | 85 | ### Parametric Line 86 | 87 | You can create parametrics lines for joining two vertices (points), these lines can be used with Warehouse Profile as well... 88 | 89 | 1. one can hide profiles objects with [Space Bar] (it allows to see the sketches) 90 | 91 | ![Hide profile](images/22-hide-profiles.png) 92 | 93 | 2. Selects vertices 94 | 95 | ![Select Vertices](images/23-select-vertexes.png) 96 | 97 | 98 | 3. Create Parametric Line 99 | 100 | ![Create parametric line](images/24-create-parametric-line.png) 101 | 102 | ![Parametric Line](images/25-parametric-line.png) 103 | 104 | 105 | You can therefore use Create profile again to create the four vertical beams ! 106 | 107 | 1. Open Create Profile, select the profile you want 108 | 2. Select the Parametric lines, click OK. 109 | 110 | ![Cube Done](images/26-cube-done.png) 111 | 112 | 113 | 114 | ### More Sketches / Part2 ! 115 | 116 | There is another ways to add sketches, that allows to do more complicated stuff... 117 | 118 | Sometime you want add a sketch to a specific place, and link it to another sketch. (If you modify the first Sketch, then the second will follow, hopefully) 119 | 120 | This is not possible with the Position / Base Placement, that is an absolute position. 121 | 122 | We are going to "Map" the sketch to something else. 123 | 124 | 1. Create a new Sketch, and set orientation to: YZ 125 | 126 | I added a circle to the sketch so you can see where it is.. (just for reference !) 127 | 128 | ![Map Mode Sketch](images/30-mapmode-sketch.png) 129 | 130 | 2. Click on the map mode property: 131 | 132 | ![Map mode](images/31-mapmode.png) 133 | 134 | ![Map Mode dialog](images/32-mapmode-dialog.png) 135 | 136 | 137 | You can change the map mode, selecting faces, vertices and edges... 138 | 139 | ![alt text](images/33-mapmode.png) 140 | 141 | Here, our circle is in a new plan, the one at the top left of the screen... 142 | 143 | There are a lot of options here. 144 | 145 | You can then edit the sketch, and create more line and frames... 146 | 147 | ## Bevels and corners. 148 | 149 | As you can see, the junctions are not that good (yet !). The profiles are centered on the skeleton, and stops right at the end of the edges. 150 | 151 | We are going to make corners, and bevels. There are two methods for that. 152 | 153 | 154 | ### Via Bevels property 155 | 156 | It is my favorite for simple frame.. 157 | 158 | Let's hide everything except the first frame we made... 159 | 160 | ![alt text](images/40-show-first-frame.png) 161 | 162 | 1. Select one of the profile, and in the property section, go for Bevel Start/End Cut 1/2 163 | 164 | ![alt text](images/41-bevels.png) 165 | 166 | There are 4 entries (Start / End Cut1 Cut2) 167 | 168 | That allows you to create bevels in the two axis, at the start or end of the profile. 169 | 170 | Negative angles works, and must be used to compensate directions. 171 | 172 | You can batch-modify that, by selecting all the profiles.... 173 | 174 | ![alt text](images/42-batchs-bevels.png) 175 | 176 | **And Voila ! a square frame !** 177 | 178 | 179 | ### Via End Miter Command 180 | 181 | Let's show the other base frame ... 182 | 183 | ![alt text](images/50-base-config.png) 184 | 185 | We first must add offsets to the existing profiles... (offsets adds up to the dimension of the edge !) 186 | 187 | 1. add Offset (One profile by one, Or selecting all the profiles and change the offset.) 188 | 189 | ![alt text](images/51-add-offset.png) 190 | 191 | 2. Unselect all objects, then select two touching Profiles. (**select faces in the 3D view, not objects in the tree view**) 192 | 193 | ![alt text](images/52-select-touching-profiles.png) 194 | 195 | 1. Click on the Create Miter End Command 196 | 197 | ![alt text](images/53-create-miter-end.png) 198 | 199 | **And voila !** You have two "TrimmedProfile" 200 | 201 | ![alt text](images/54-miter-end.png) 202 | 203 | 204 | ### Via End Trim Command 205 | 206 | Let's finish the 3 others corners of the second frame... 207 | 208 | ![alt text](images/60-startwith.png) 209 | 210 | ![alt text](images/61-bad-joint.png) 211 | 212 | When everything is showed again, you can see the vertical profiles are not cut as they should... 213 | 214 | Let's open again the corner manager, selecting "end trim" 215 | 216 | ![alt text](images/62-endtrim.png) 217 | 218 | 219 | ![alt text](images/62-endtrim-task.png) 220 | 221 | 1. Select the vertical profile first, add it to the trimmed object with the plus (+) button 222 | 223 | ![alt text](images/63-select-trimmed-body-1.png) 224 | ![alt text](images/63-select-trimmed-body-2.png) 225 | 226 | 2. Select the face of the profile you want to cut with.. (here, I add to move the view and select the bottom **face**) 227 | 228 | ![alt text](images/64-select-trimming-boundaries-1.png) 229 | ![alt text](images/64-select-trimming-boundaries-2.png) 230 | 231 | You can change the cut type: straight or following the other profile. 232 | 233 | ![alt text](images/64-select-cuttype-1.png) 234 | ![alt text](images/64-select-cuttype-2.png) 235 | 236 | 237 | And you also can add faces related to the other side of the trimmed profile. 238 | 239 | ## Organizing Objects 240 | 241 | That's the bad part. 242 | 243 | I find the tree view messy. Really messy. 244 | 245 | ### Part Container 246 | 247 | I often use Part container for grouping profiles, sketches, etc. 248 | 249 | ![alt text](images/70-part-container.png) 250 | 251 | ![alt text](images/71-part-container.png) 252 | 253 | You should drag only one profile to the container... I don't know why, but FreeCAD is not happy about a group drag. 254 | 255 | Sometime parts and profile get out of the Part Container. 256 | 257 | 258 | 259 | ### Fusion 260 | 261 | One can fuse profiles together. 262 | 263 | ![alt text](images/72-fusion.png) 264 | 265 | ![alt text](images/72-fusion-done.png) 266 | 267 | It allows to group objects. 268 | 269 | 270 | ## Using profiles in Part Design... ie, making holes ! 271 | 272 | To use all of these profiles in PartDesign, for instance, to make holes... in it.. ! 273 | 274 | you need to use a fusion of the profile, and create a body... 275 | 276 | ![Body](images/80-body.png) 277 | 278 | 1. Drag and drop the fusion into the body. 279 | 280 | ![base feature](images/81-basefeature.png) 281 | 282 | 2. Now, you have a standard Part design Body... 283 | 284 | You can map a sketch to any face, and use Part design to do whatever you want ! 285 | 286 | ![Making Holes](images/82-making-holes.png) 287 | 288 | ![Holes Made](images/83-holes-made.png) -------------------------------------------------------------------------------- /freecad/frameforge/__init__.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | RESSOURCESPATH = os.path.join(os.path.dirname(__file__), "resources") 4 | 5 | PROFILESPATH = os.path.join(RESSOURCESPATH, "profiles") 6 | 7 | ICONPATH = os.path.join(RESSOURCESPATH, "icons") 8 | PROFILEIMAGES_PATH = os.path.join(RESSOURCESPATH, "images", "profiles") 9 | UIPATH = os.path.join(RESSOURCESPATH, "ui") 10 | TRANSLATIONSPATH = os.path.join(RESSOURCESPATH, "translations") -------------------------------------------------------------------------------- /freecad/frameforge/_utils.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | __title__ = "Curves workbench utilities" 4 | __author__ = "Christophe Grellier (Chris_G)" 5 | __license__ = "LGPL 2.1" 6 | __doc__ = "Curves workbench utilities common to all tools." 7 | 8 | import FreeCAD 9 | import Part 10 | 11 | 12 | def getSubShape(shape, shape_type, n): 13 | if shape_type == "Vertex" and len(shape.Vertexes) >= n: 14 | return shape.Vertexes[n - 1] 15 | elif shape_type == "Edge" and len(shape.Edges) >= n: 16 | return shape.Edges[n - 1] 17 | elif shape_type == "Face" and len(shape.Faces) >= n: 18 | return shape.Faces[n - 1] 19 | else: 20 | return None 21 | 22 | 23 | def getShape(obj, prop, shape_type): 24 | if hasattr(obj, prop) and obj.getPropertyByName(prop): 25 | prop_link = obj.getPropertyByName(prop) 26 | if obj.getTypeIdOfProperty(prop) == "App::PropertyLinkSub": 27 | if shape_type in prop_link[1][0]: 28 | # try: # FC 0.19+ 29 | # return prop_link[0].getSubObject(prop_link[1][0]) 30 | # except AttributeError: # FC 0.18 (stable) 31 | n = eval(obj.getPropertyByName(prop)[1][0].lstrip(shape_type)) 32 | osh = obj.getPropertyByName(prop)[0].Shape 33 | sh = osh.copy() 34 | if sh and (not shape_type == "Vertex") and hasattr(obj.getPropertyByName(prop)[0], "getGlobalPlacement"): 35 | pl = obj.getPropertyByName(prop)[0].getGlobalPlacement() 36 | sh.Placement = pl 37 | return getSubShape(sh, shape_type, n) 38 | 39 | elif obj.getTypeIdOfProperty(prop) == "App::PropertyLinkSubList": 40 | res = [] 41 | for tup in prop_link: 42 | for ss in tup[1]: 43 | if shape_type in ss: 44 | # try: # FC 0.19+ 45 | # res.append(tup[0].getSubObject(ss)) 46 | # except AttributeError: # FC 0.18 (stable) 47 | n = eval(ss.lstrip(shape_type)) 48 | sh = tup[0].Shape.copy() 49 | if sh and (not shape_type == "Vertex") and hasattr(tup[0], "getGlobalPlacement"): 50 | pl = tup[0].getGlobalPlacement() 51 | sh.Placement = pl 52 | res.append(getSubShape(sh, shape_type, n)) 53 | return res 54 | else: 55 | FreeCAD.Console.PrintError("CurvesWB._utils.getShape: wrong property type.\n") 56 | return None 57 | else: 58 | # FreeCAD.Console.PrintError("CurvesWB._utils.getShape: %r has no property %r\n"%(obj, prop)) 59 | return None 60 | 61 | -------------------------------------------------------------------------------- /freecad/frameforge/create_end_miter_tool.py: -------------------------------------------------------------------------------- 1 | import os, glob 2 | import json 3 | 4 | from PySide import QtCore, QtGui 5 | 6 | import FreeCADGui as Gui 7 | import FreeCAD as App 8 | 9 | import Part, ArchCommands 10 | import BOPTools.SplitAPI 11 | 12 | from freecad.frameforge.translate_utils import translate 13 | from freecad.frameforge import PROFILESPATH, PROFILEIMAGES_PATH, ICONPATH, UIPATH 14 | 15 | from freecad.frameforge.trimmed_profile import TrimmedProfile, ViewProviderTrimmedProfile 16 | 17 | 18 | 19 | class CreateEndMiterCommand(): 20 | def GetResources(self): 21 | return { 22 | "Pixmap": os.path.join(ICONPATH, "corner-end-miter.svg"), 23 | "MenuText": translate("MetalWB", "Create Miter Ends"), 24 | "Accel": "M, C", 25 | "ToolTip": translate("MetalWB", "

Create Miter Ends \ 26 |

\ 27 | Select two profiles. \ 28 |

"), 29 | } 30 | 31 | def IsActive(self): 32 | if App.ActiveDocument: 33 | if len(Gui.Selection.getSelection()) == 2: 34 | active = False 35 | for sel in Gui.Selection.getSelection(): 36 | if hasattr(sel, 'Target'): 37 | active = True 38 | elif hasattr(sel, 'TrimmedBody'): 39 | active = True 40 | else: 41 | return False 42 | return active 43 | return False 44 | 45 | def Activated(self): 46 | # create a TrimmedProfile object 47 | sel = Gui.Selection.getSelectionEx() 48 | App.ActiveDocument.openTransaction("Make End Miter Profile") 49 | 50 | if len(sel) == 2: 51 | self.make_end_miter_profile(sel[0].Object, [(sel[1].Object, sel[1].SubElementNames)]) 52 | self.make_end_miter_profile(sel[1].Object, [(sel[0].Object, sel[0].SubElementNames)]) 53 | 54 | App.ActiveDocument.commitTransaction() 55 | App.ActiveDocument.recompute() 56 | 57 | def make_end_miter_profile(self, trimmedBody=None, trimmingBoundary=None): 58 | doc = App.ActiveDocument 59 | 60 | trimmed_profile = doc.addObject("Part::FeaturePython","TrimmedProfile") 61 | 62 | if trimmedBody is not None and len(trimmedBody.Parents) > 0: 63 | trimmedBody.Parents[-1][0].addObject(trimmed_profile) 64 | 65 | TrimmedProfile(trimmed_profile) 66 | 67 | ViewProviderTrimmedProfile(trimmed_profile.ViewObject) 68 | trimmed_profile.TrimmedBody = trimmedBody 69 | trimmed_profile.TrimmingBoundary = trimmingBoundary 70 | 71 | trimmed_profile.TrimmedProfileType = "End Miter" 72 | 73 | # doc.recompute() 74 | return trimmed_profile 75 | 76 | Gui.addCommand("FrameForge_EndMiter", CreateEndMiterCommand()) 77 | -------------------------------------------------------------------------------- /freecad/frameforge/create_profiles_tool.py: -------------------------------------------------------------------------------- 1 | import os, glob 2 | import json 3 | 4 | from PySide import QtCore, QtGui 5 | 6 | import FreeCADGui as Gui 7 | import FreeCAD as App 8 | 9 | from freecad.frameforge.translate_utils import translate 10 | from freecad.frameforge import PROFILESPATH, PROFILEIMAGES_PATH, ICONPATH, UIPATH 11 | 12 | from freecad.frameforge.profile import Profile 13 | 14 | class CreateProfileTaskPanel(): 15 | def __init__(self): 16 | ui_file = os.path.join(UIPATH, "create_profiles.ui") 17 | self.form = Gui.PySideUic.loadUi(ui_file) 18 | 19 | self.load_data() 20 | self.initialize_ui() 21 | 22 | 23 | def load_data(self): 24 | self.profiles = {} 25 | 26 | files = [f for f in os.listdir(PROFILESPATH) if f.endswith('.json')] 27 | 28 | for f in files: 29 | material_name = os.path.splitext(f)[0].capitalize() 30 | 31 | with open(os.path.join(PROFILESPATH, f)) as fd: 32 | self.profiles[material_name] = json.load(fd) 33 | 34 | 35 | def initialize_ui(self): 36 | self.form.label_image.setPixmap(QtGui.QPixmap(os.path.join(PROFILEIMAGES_PATH, "Warehouse.png"))) 37 | 38 | self.form.combo_material.currentIndexChanged.connect(self.on_material_changed) 39 | self.form.combo_family.currentIndexChanged.connect(self.on_family_changed) 40 | self.form.combo_size.currentIndexChanged.connect(self.on_size_changed) 41 | 42 | self.form.cb_make_fillet.stateChanged.connect(self.on_cb_make_fillet_changed) 43 | 44 | self.form.combo_material.addItems([k for k in self.profiles]) 45 | 46 | 47 | param = App.ParamGet("User parameter:BaseApp/Preferences/Frameforge") 48 | default_material_index = self.form.combo_material.findText(param.GetString("Default Profile Material")) 49 | if default_material_index > -1: 50 | self.form.combo_material.setCurrentIndex(default_material_index) 51 | 52 | default_family_index = self.form.combo_family.findText(param.GetString("Default Profile Family")) 53 | if default_family_index > -1: 54 | self.form.combo_family.setCurrentIndex(default_family_index) 55 | 56 | default_size_index = self.form.combo_size.findText(param.GetString("Default Profile Size")) 57 | if default_size_index > -1: 58 | self.form.combo_size.setCurrentIndex(default_size_index) 59 | 60 | 61 | def on_material_changed(self, index): 62 | material = str(self.form.combo_material.currentText()) 63 | 64 | self.form.combo_family.clear() 65 | self.form.combo_family.addItems([f for f in self.profiles[material]]) 66 | 67 | def on_family_changed(self, index): 68 | material = str(self.form.combo_material.currentText()) 69 | family = str(self.form.combo_family.currentText()) 70 | 71 | self.form.cb_make_fillet.setChecked(self.profiles[material][family]['fillet']) 72 | self.form.cb_make_fillet.setEnabled(self.profiles[material][family]['fillet']) 73 | 74 | self.update_image() 75 | 76 | self.form.label_norm.setText(self.profiles[material][family]['norm']) 77 | self.form.label_unit.setText(self.profiles[material][family]['unit']) 78 | 79 | self.form.combo_size.clear() 80 | self.form.combo_size.addItems([s for s in self.profiles[material][family]['sizes']]) 81 | 82 | 83 | def on_size_changed(self, index): 84 | material = str(self.form.combo_material.currentText()) 85 | family = str(self.form.combo_family.currentText()) 86 | size = str(self.form.combo_size.currentText()) 87 | 88 | if size != '': 89 | profile = self.profiles[material][family]['sizes'][size] 90 | 91 | SETTING_MAP = { 92 | "Height": self.form.sb_height, 93 | "Width": self.form.sb_width, 94 | "Thickness": self.form.sb_main_thickness, 95 | "Flange Thickness": self.form.sb_flange_thickness, 96 | "Radius1": self.form.sb_radius1, 97 | "Radius2": self.form.sb_radius2, 98 | "Weight": self.form.sb_weight 99 | } 100 | 101 | self.form.sb_height.setEnabled(False) 102 | self.form.sb_width.setEnabled(False) 103 | self.form.sb_main_thickness.setEnabled(False) 104 | self.form.sb_flange_thickness.setEnabled(False) 105 | self.form.sb_radius1.setEnabled(False) 106 | self.form.sb_radius2.setEnabled(False) 107 | self.form.sb_weight.setEnabled(False) 108 | 109 | for s in profile: 110 | if s == "Size": 111 | continue 112 | 113 | if s not in SETTING_MAP: 114 | raise ValueError('Setting Unkown') 115 | 116 | sb = SETTING_MAP[s] 117 | sb.setEnabled(True) 118 | 119 | sb.setValue(float(profile[s])) 120 | 121 | 122 | 123 | def on_cb_make_fillet_changed(self, state): 124 | self.update_image() 125 | 126 | 127 | 128 | def update_image(self): 129 | material = str(self.form.combo_material.currentText()) 130 | family = str(self.form.combo_family.currentText()) 131 | 132 | img_name = family.replace(' ', "_") 133 | if self.form.cb_make_fillet.isChecked(): 134 | img_name += "_Fillet" 135 | img_name += ".png" 136 | 137 | self.form.label_image.setPixmap(QtGui.QPixmap(os.path.join(PROFILEIMAGES_PATH, material, img_name))) 138 | 139 | 140 | def open(self): 141 | App.Console.PrintMessage(translate("frameforge", "Opening CreateProfile\n")) 142 | self.update_selection() 143 | 144 | App.ActiveDocument.openTransaction("Add Profile") 145 | 146 | 147 | def reject(self): 148 | App.Console.PrintMessage(translate("frameforge", "Rejecting CreateProfile\n")) 149 | 150 | self.clean() 151 | App.ActiveDocument.abortTransaction() 152 | 153 | return True 154 | 155 | 156 | def accept(self): 157 | if len(Gui.Selection.getSelectionEx()) or self.form.sb_length.value() > 0: 158 | App.Console.PrintMessage(translate("frameforge", "Accepting CreateProfile\n")) 159 | 160 | param = App.ParamGet("User parameter:BaseApp/Preferences/Frameforge") 161 | param.SetString("Default Profile Material", self.form.combo_material.currentText()) 162 | param.SetString("Default Profile Family", self.form.combo_family.currentText()) 163 | param.SetString("Default Profile Size", self.form.combo_size.currentText()) 164 | 165 | self.proceed() 166 | self.clean() 167 | 168 | App.ActiveDocument.commitTransaction() 169 | App.ActiveDocument.recompute() 170 | 171 | return True 172 | 173 | else: 174 | App.Console.PrintMessage(translate("frameforge", "Not Accepting CreateProfile\nSelect Edges or set Length")) 175 | 176 | diag = QtGui.QMessageBox(QtGui.QMessageBox.Warning, 'Create Profile', 'Select Edges or set Length to create a profile') 177 | diag.setWindowModality(QtCore.Qt.ApplicationModal) 178 | diag.exec_() 179 | 180 | return False 181 | 182 | 183 | def clean(self): 184 | Gui.Selection.removeObserver(self) 185 | Gui.Selection.removeSelectionGate() 186 | 187 | 188 | def proceed(self): 189 | selection_list = Gui.Selection.getSelectionEx() 190 | 191 | p_name = "Profile" 192 | if len(selection_list) == 1 and self.form.cb_sketch_in_name.isChecked(): 193 | sketch_sel = selection_list[0] 194 | 195 | p_name += "_" + sketch_sel.Object.Name 196 | 197 | if self.form.cb_family_in_name.isChecked(): 198 | p_name += "_" + self.form.combo_family.currentText().replace(" ", "_") 199 | 200 | if self.form.cb_size_in_name.isChecked(): 201 | p_name += "_" + self.form.combo_size.currentText() 202 | 203 | if len(selection_list): 204 | # create part or group and 205 | container = None 206 | if self.form.rb_profiles_in_part.isChecked(): 207 | container = App.activeDocument().addObject('App::Part','Part') 208 | # elif self.form.rb_profiles_in_group.isChecked(): # not working 209 | # container = App.activeDocument().addObject('App::DocumentObjectGroup','Group') 210 | 211 | # creates profiles 212 | for sketch_sel in selection_list: 213 | # move the sketch inside the container 214 | if container: 215 | container.addObject(sketch_sel.Object) 216 | 217 | if len(sketch_sel.SubElementNames) > 0: 218 | edges = sketch_sel.SubElementNames 219 | else: #use on the whole sketch 220 | edges = [f"Edge{idx + 1}" for idx, e in enumerate(sketch_sel.Object.Shape.Edges)] 221 | 222 | for i, edge in enumerate(edges): 223 | self.make_profile(sketch_sel.Object, edge, p_name) 224 | 225 | else: 226 | self.make_profile(None, None, p_name) 227 | 228 | 229 | 230 | def make_profile(self, sketch, edge, name): 231 | # Create an object in current document 232 | obj = App.ActiveDocument.addObject("Part::FeaturePython", name) 233 | obj.addExtension("Part::AttachExtensionPython") 234 | 235 | # move it to the sketch's parent if possible 236 | if sketch is not None and len(sketch.Parents) > 0: 237 | sk_parent = sketch.Parents[-1][0] 238 | sk_parent.addObject(obj) 239 | 240 | # Create a ViewObject in current GUI 241 | obj.ViewObject.Proxy = 0 242 | view_obj = Gui.ActiveDocument.getObject(obj.Name) 243 | view_obj.DisplayMode = "Flat Lines" 244 | 245 | 246 | if sketch is not None and edge is not None: 247 | # Tuple assignment for edge 248 | feature = sketch 249 | link_sub = (feature, (edge)) 250 | obj.MapMode = "NormalToEdge" 251 | 252 | try: 253 | obj.AttachmentSupport = (feature, edge) 254 | except AttributeError: # for Freecad <= 0.21 support 255 | obj.Support = (feature, edge) 256 | 257 | else: 258 | link_sub = None 259 | 260 | if not self.form.cb_reverse_attachment.isChecked(): 261 | #print("Not reverse attachment") 262 | obj.MapPathParameter = 1 263 | else: 264 | #print("Reverse attachment") 265 | obj.MapPathParameter = 0 266 | obj.MapReversed = True 267 | 268 | 269 | Profile( 270 | obj, 271 | self.form.sb_width.value(), 272 | self.form.sb_height.value(), 273 | self.form.sb_main_thickness.value(), 274 | self.form.sb_flange_thickness.value(), 275 | self.form.sb_radius1.value(), 276 | self.form.sb_radius2.value(), 277 | self.form.sb_length.value(), 278 | self.form.sb_weight.value(), 279 | self.form.cb_make_fillet.isChecked(), # and self.form.family.currentText() not in ["Flat Sections", "Square", "Round Bar"], 280 | self.form.cb_height_centered.isChecked(), 281 | self.form.cb_width_centered.isChecked(), 282 | self.form.combo_family.currentText(), 283 | self.form.cb_combined_bevel.isChecked(), 284 | link_sub 285 | ) 286 | 287 | 288 | 289 | def addSelection(self, doc, obj, sub, other): 290 | self.update_selection() 291 | 292 | def clearSelection(self, other): 293 | self.update_selection() 294 | 295 | def update_selection(self): 296 | if len(Gui.Selection.getSelectionEx()) > 0: 297 | self.form.sb_length.setEnabled(False) 298 | self.form.sb_length.setValue(0.0) 299 | 300 | obj_name = '' 301 | for sel in Gui.Selection.getSelectionEx(): 302 | selected_obj_name = sel.ObjectName 303 | subs = '' 304 | for sub in sel.SubElementNames: 305 | subs += '{},'.format(sub) 306 | 307 | obj_name += selected_obj_name 308 | obj_name += " / " 309 | obj_name += subs 310 | # obj_name += '\n' 311 | 312 | else: 313 | self.form.sb_length.setEnabled(True) 314 | obj_name = 'Not Attached / Define length' 315 | 316 | 317 | self.form.label_attach.setText(obj_name) 318 | 319 | 320 | 321 | class CreateProfilesCommand(): 322 | """Create Profiles with standards dimensions""" 323 | 324 | def GetResources(self): 325 | return {"Pixmap" : os.path.join(ICONPATH, "warehouse_profiles.svg"), 326 | "Accel" : "Shift+S", # a default shortcut (optional) 327 | "MenuText": "Create Profile", 328 | "ToolTip" : "Create new profiles from Edges"} 329 | 330 | def Activated(self): 331 | """Do something here""" 332 | panel = CreateProfileTaskPanel() 333 | 334 | Gui.Selection.addObserver(panel) 335 | 336 | Gui.Control.showDialog(panel) 337 | 338 | 339 | def IsActive(self): 340 | """Here you can define if the command must be active or not (greyed) if certain conditions 341 | are met or not. This function is optional.""" 342 | return App.ActiveDocument is not None 343 | 344 | Gui.addCommand("FrameForge_CreateProfiles", CreateProfilesCommand()) -------------------------------------------------------------------------------- /freecad/frameforge/create_trimmed_profiles_tool.py: -------------------------------------------------------------------------------- 1 | import os, glob 2 | import json 3 | 4 | from PySide import QtCore, QtGui 5 | 6 | import FreeCADGui as Gui 7 | import FreeCAD as App 8 | 9 | import Part, ArchCommands 10 | import BOPTools.SplitAPI 11 | 12 | from freecad.frameforge.translate_utils import translate 13 | from freecad.frameforge import PROFILESPATH, PROFILEIMAGES_PATH, ICONPATH, UIPATH 14 | 15 | from freecad.frameforge.trimmed_profile import TrimmedProfile, ViewProviderTrimmedProfile 16 | 17 | 18 | 19 | 20 | class CreateTrimmedProfileTaskPanel(): 21 | def __init__(self, fp, mode): 22 | ui_file = os.path.join(UIPATH, "create_trimmed_profiles.ui") 23 | self.form = Gui.PySideUic.loadUi(ui_file) 24 | 25 | 26 | self.fp = fp 27 | self.dump = fp.dumpContent() 28 | self.mode=mode 29 | 30 | self.initialize_ui() 31 | self.update_view_and_model() 32 | 33 | 34 | def initialize_ui(self): 35 | add_icon = QtGui.QIcon(os.path.join(ICONPATH, "list-add.svg")) 36 | remove_icon = QtGui.QIcon(os.path.join(ICONPATH, "list-remove.svg")) 37 | coped_type_icon = QtGui.QIcon(os.path.join(ICONPATH, "corner-coped-type.svg")) 38 | simple_type_icon = QtGui.QIcon(os.path.join(ICONPATH, "corner-simple-type.svg")) 39 | 40 | QSize = QtCore.QSize(32, 32) 41 | 42 | self.form.rb_copedcut.setIcon(coped_type_icon) 43 | self.form.rb_copedcut.setIconSize(QSize) 44 | self.form.rb_copedcut.toggled.connect(lambda: self.update_cuttype("Coped cut")) 45 | 46 | self.form.rb_simplecut.setIcon(simple_type_icon) 47 | self.form.rb_simplecut.setIconSize(QSize) 48 | self.form.rb_simplecut.toggled.connect(lambda: self.update_cuttype("Simple cut")) 49 | 50 | param = App.ParamGet("User parameter:BaseApp/Preferences/Frameforge") 51 | if param.GetString("Default Cut Type") == "Coped cut": 52 | self.form.rb_copedcut.toggle() 53 | elif param.GetString("Default Cut Type") == "Simple cut": 54 | self.form.rb_simplecut.toggle() 55 | 56 | self.form.add_trimmed_object_button.setIcon(add_icon) 57 | self.form.add_boundary_button.setIcon(add_icon) 58 | self.form.remove_boundary_button.setIcon(remove_icon) 59 | 60 | self.form.add_trimmed_object_button.clicked.connect(self.set_trimmed_body) 61 | self.form.add_boundary_button.clicked.connect(self.add_trimming_bodies) 62 | self.form.remove_boundary_button.clicked.connect(self.remove_trimming_bodies) 63 | 64 | 65 | def update_cuttype(self, cuttype): 66 | self.fp.CutType = cuttype 67 | 68 | self.update_view_and_model() 69 | 70 | 71 | def set_trimmed_body(self): 72 | if len(Gui.Selection.getSelectionEx()) == 1: 73 | trimmed_body = Gui.Selection.getSelectionEx()[0].Object 74 | App.Console.PrintMessage(translate("frameforge", f"Set Trimmed body: {trimmed_body.Name}\n")) 75 | self.fp.TrimmedBody = trimmed_body 76 | 77 | if len(trimmed_body.Parents) > 0: 78 | trimmed_body.Parents[-1][0].addObject(self.fp) 79 | 80 | self.update_view_and_model() 81 | 82 | 83 | 84 | def add_trimming_bodies(self): 85 | App.Console.PrintMessage(translate("frameforge", "Add Trimming bodies...\n")) 86 | 87 | # It looks like the TrimmingBoundary list must be rebuilt, not working if trying to only append data.. 88 | trimming_boundaries = [e for e in self.fp.TrimmingBoundary] 89 | 90 | for selObject in Gui.Selection.getSelectionEx(): 91 | if all([tb != (selObject.Object, tuple(selObject.SubElementNames)) for tb in trimming_boundaries]): 92 | trimming_boundaries.append((selObject.Object, tuple(selObject.SubElementNames))) 93 | 94 | App.Console.PrintMessage(translate("frameforge", f"\tadd trimming body: {selObject.ObjectName}, {tuple(selObject.SubElementNames)}\n")) 95 | 96 | else: 97 | App.Console.PrintMessage(translate("frameforge", "Already a trimming body for this TrimmedBody\n")) 98 | 99 | 100 | self.fp.TrimmingBoundary = trimming_boundaries 101 | 102 | self.update_view_and_model() 103 | 104 | def remove_trimming_bodies(self): 105 | App.Console.PrintMessage(translate("frameforge", "Remove Trimming body\n")) 106 | 107 | selected_tb = [item.data(1) for item in self.form.boundaries_list_widget.selectedItems()] 108 | self.fp.TrimmingBoundary = [tb for tb in self.fp.TrimmingBoundary if tb not in selected_tb] 109 | 110 | self.update_view_and_model() 111 | 112 | 113 | 114 | def update_view_and_model(self): 115 | if self.fp.TrimmedBody is not None: 116 | self.form.trimmed_object_label.setText("{} ({})".format(self.fp.TrimmedBody.Label, self.fp.TrimmedBody.Name)) 117 | else: 118 | self.form.trimmed_object_label.setText("Select...") 119 | 120 | self.form.boundaries_list_widget.clear() 121 | 122 | # if self.fp.TrimmingBoundary is not None and len(self.fp.TrimmingBoundary) > 0: 123 | for bound in self.fp.TrimmingBoundary: 124 | item = QtGui.QListWidgetItem() 125 | item.setText("{} ({} {})".format(bound[0].Label, bound[0].Name, ", ".join(bound[1]))) 126 | item.setData(1, bound) 127 | self.form.boundaries_list_widget.addItem(item) 128 | 129 | 130 | self.fp.recompute() 131 | 132 | 133 | def open(self): 134 | App.Console.PrintMessage(translate("frameforge", "Opening Create Trimmed Profile\n")) 135 | App.ActiveDocument.openTransaction("Update Trim") 136 | 137 | 138 | def reject(self): 139 | App.Console.PrintMessage(translate("frameforge", f"Rejecting CreateProfile {self.mode}\n")) 140 | 141 | if self.mode == "edition": 142 | self.fp.restoreContent(self.dump) 143 | Gui.ActiveDocument.resetEdit() 144 | 145 | elif self.mode == "creation": 146 | trimmedBody = self.fp.TrimmedBody 147 | 148 | App.ActiveDocument.removeObject(self.fp.Name) 149 | 150 | if trimmedBody: 151 | trimmedBody.ViewObject.Visibility = True 152 | 153 | App.ActiveDocument.commitTransaction() 154 | 155 | App.ActiveDocument.recompute() 156 | Gui.ActiveDocument.resetEdit() 157 | 158 | return True 159 | 160 | 161 | def accept(self): 162 | App.Console.PrintMessage(translate("frameforge", "Accepting Create Trimmed Profile\n")) 163 | 164 | param = App.ParamGet("User parameter:BaseApp/Preferences/Frameforge") 165 | param.SetString("Default Cut Type", self.fp.CutType) 166 | 167 | 168 | App.ActiveDocument.commitTransaction() 169 | 170 | App.ActiveDocument.recompute() 171 | Gui.ActiveDocument.resetEdit() 172 | 173 | return True 174 | 175 | 176 | 177 | 178 | class TrimProfileCommand(): 179 | def GetResources(self): 180 | return { 181 | "Pixmap": os.path.join(ICONPATH, "corner-end-trim.svg"), 182 | "MenuText": translate("MetalWB", "Trim Profile"), 183 | "Accel": "M, C", 184 | "ToolTip": translate("MetalWB", "

Trim a profile \ 185 |

\ 186 | Select a profile then another profile's faces. \ 187 |

"), 188 | } 189 | 190 | def IsActive(self): 191 | if App.ActiveDocument: 192 | if len(Gui.Selection.getSelection()) > 0: 193 | active = False 194 | for sel in Gui.Selection.getSelection(): 195 | if hasattr(sel, 'Target'): 196 | active = True 197 | elif hasattr(sel, 'TrimmedBody'): 198 | active = True 199 | else: 200 | return False 201 | return active 202 | else: 203 | return True 204 | return False 205 | 206 | def Activated(self): 207 | # create a TrimmedProfile object 208 | sel = Gui.Selection.getSelectionEx() 209 | App.ActiveDocument.openTransaction("Make Trimmed Profile") 210 | if len(sel) == 0: 211 | trimmed_profile = self.make_trimmed_profile() 212 | elif len(sel) == 1: 213 | trimmed_profile = self.make_trimmed_profile(trimmedBody=sel[0].Object) 214 | elif len(sel) > 1 : 215 | trimmingboundary = [] 216 | for selectionObject in sel[1:]: 217 | bound = (selectionObject.Object, selectionObject.SubElementNames) 218 | trimmingboundary.append(bound) 219 | trimmed_profile = self.make_trimmed_profile(trimmedBody=sel[0].Object, trimmingBoundary=trimmingboundary) 220 | App.ActiveDocument.commitTransaction() 221 | 222 | 223 | panel = CreateTrimmedProfileTaskPanel(trimmed_profile, mode="creation") 224 | Gui.Control.showDialog(panel) 225 | 226 | 227 | 228 | def make_trimmed_profile(self, trimmedBody=None, trimmingBoundary=None): 229 | doc = App.ActiveDocument 230 | 231 | trimmed_profile = doc.addObject("Part::FeaturePython","TrimmedProfile") 232 | 233 | if trimmedBody is not None and len(trimmedBody.Parents) > 0: 234 | trimmedBody.Parents[-1][0].addObject(trimmed_profile) 235 | 236 | TrimmedProfile(trimmed_profile) 237 | 238 | ViewProviderTrimmedProfile(trimmed_profile.ViewObject) 239 | trimmed_profile.TrimmedBody = trimmedBody 240 | trimmed_profile.TrimmingBoundary = trimmingBoundary 241 | 242 | trimmed_profile.TrimmedProfileType = "End Trim" 243 | 244 | # doc.recompute() 245 | return trimmed_profile 246 | 247 | Gui.addCommand("FrameForge_TrimProfiles", TrimProfileCommand()) 248 | -------------------------------------------------------------------------------- /freecad/frameforge/init_gui.py: -------------------------------------------------------------------------------- 1 | import os 2 | import FreeCADGui as Gui 3 | import FreeCAD as App 4 | from freecad.frameforge.translate_utils import translate 5 | 6 | from freecad.frameforge import ICONPATH, TRANSLATIONSPATH 7 | 8 | 9 | class FrameForge(Gui.Workbench): 10 | """ 11 | class which gets initiated at startup of the gui 12 | """ 13 | MenuText = translate("frameforge", "FrameForge") 14 | ToolTip = translate("frameforge", "a simple FrameForge") 15 | Icon = os.path.join(ICONPATH, "metalwb.svg") 16 | 17 | toolbox_drawing = [ 18 | "Sketcher_NewSketch", 19 | "FrameForge_ParametricLine" 20 | ] 21 | 22 | toolbox_frameforge = [ 23 | "FrameForge_CreateProfiles", 24 | "FrameForge_TrimProfiles", 25 | "FrameForge_EndMiter" 26 | ] 27 | 28 | toolbox_part = [ 29 | "Part_Fuse", 30 | "Part_Cut", 31 | 32 | "PartDesign_Body", 33 | 34 | "PartDesign_Pad", 35 | "PartDesign_Pocket" 36 | ] 37 | 38 | def GetClassName(self): 39 | return "Gui::PythonWorkbench" 40 | 41 | def Initialize(self): 42 | """ 43 | This function is called at the first activation of the workbench. 44 | here is the place to import all the commands 45 | """ 46 | from freecad.frameforge import parametric_line 47 | from freecad.frameforge import create_profiles_tool, create_trimmed_profiles_tool, create_end_miter_tool 48 | 49 | # Add translations path 50 | Gui.addLanguagePath(TRANSLATIONSPATH) 51 | Gui.updateLocale() 52 | 53 | App.Console.PrintMessage(translate( 54 | "frameforge", 55 | "Switching to frameforge") + "\n") 56 | 57 | self.appendToolbar(translate("frameforge", "Drawing Primitives"), self.toolbox_drawing) 58 | self.appendMenu(translate("frameforge", "Drawing Primitives"), self.toolbox_drawing) 59 | 60 | self.appendToolbar(translate("frameforge", "Frameforge"), self.toolbox_frameforge) 61 | self.appendMenu(translate("frameforge", "Frameforge"), self.toolbox_frameforge) 62 | 63 | self.appendToolbar(translate("frameforge", "Part Primitives"), self.toolbox_part) 64 | self.appendMenu(translate("frameforge", "Part Primitives"), self.toolbox_part) 65 | 66 | def Activated(self): 67 | ''' 68 | code which should be computed when a user switch to this workbench 69 | ''' 70 | App.Console.PrintMessage(translate( 71 | "frameforge", 72 | "Workbench frameforge activated.") + "\n") 73 | 74 | def Deactivated(self): 75 | ''' 76 | code which should be computed when this workbench is deactivated 77 | ''' 78 | App.Console.PrintMessage(translate( 79 | "frameforge", 80 | "Workbench frameforge de-activated.") + "\n") 81 | 82 | 83 | Gui.addWorkbench(FrameForge()) 84 | -------------------------------------------------------------------------------- /freecad/frameforge/parametric_line.py: -------------------------------------------------------------------------------- 1 | # Parametric line 2 | # Initial author : Christophe Grellier (Chris_G) 3 | # LGPL 2. 4 | # Parametric line between two vertexes. 5 | # Select 2 vertexes in the 3D View and activate the tool. 6 | 7 | import os 8 | import FreeCAD as App 9 | import Part 10 | 11 | if App.GuiUp: 12 | import FreeCADGui as Gui 13 | 14 | from freecad.frameforge import _utils 15 | from freecad.frameforge import ICONPATH 16 | 17 | TOOL_ICON = os.path.join(ICONPATH, "line.svg") 18 | 19 | 20 | class ParametricLine: 21 | """Creates a parametric line between two vertexes""" 22 | def __init__(self, obj): 23 | """Add the properties""" 24 | obj.addProperty("App::PropertyLinkSub", "Vertex1", "ParametricLine", "First Vertex") 25 | obj.addProperty("App::PropertyLinkSub", "Vertex2", "ParametricLine", "Second Vertex") 26 | obj.Proxy = self 27 | 28 | def execute(self, obj): 29 | v1 = _utils.getShape(obj, "Vertex1", "Vertex") 30 | v2 = _utils.getShape(obj, "Vertex2", "Vertex") 31 | if v1 and v2: 32 | ls = Part.LineSegment(v1.Point, v2.Point) 33 | obj.Shape = ls.toShape() 34 | else: 35 | App.Console.PrintError("{} broken !\n".format(obj.Label)) 36 | 37 | 38 | class ParametricLineViewProvider: 39 | def __init__(self, vobj): 40 | vobj.Proxy = self 41 | 42 | def getIcon(self): 43 | return TOOL_ICON 44 | 45 | def attach(self, vobj): 46 | self.Object = vobj.Object 47 | 48 | def __getstate__(self): 49 | return {"name": self.Object.Name} 50 | 51 | def __setstate__(self, state): 52 | self.Object = App.ActiveDocument.getObject(state["name"]) 53 | return None 54 | 55 | 56 | class CreateParametricLineCommand: 57 | """Creates a parametric line between two vertexes""" 58 | def make_parametric_line(self, source): 59 | line_object = App.ActiveDocument.addObject("Part::FeaturePython", "ParametricLine") 60 | 61 | ParametricLine(line_object) 62 | ParametricLineViewProvider(line_object.ViewObject) 63 | 64 | line_object.Vertex1 = source[0] 65 | line_object.Vertex2 = source[1] 66 | App.ActiveDocument.recompute() 67 | 68 | def Activated(self): 69 | verts = [] 70 | sel = Gui.Selection.getSelectionEx() 71 | for selobj in sel: 72 | if selobj.HasSubObjects: 73 | for i in range(len(selobj.SubObjects)): 74 | if isinstance(selobj.SubObjects[i], Part.Vertex): 75 | verts.append((selobj.Object, selobj.SubElementNames[i])) 76 | if len(verts) == 2: 77 | self.make_parametric_line(verts) 78 | 79 | def IsActive(self): 80 | if App.ActiveDocument: 81 | verts = [] 82 | sel = Gui.Selection.getSelectionEx() 83 | for selobj in sel: 84 | if selobj.HasSubObjects: 85 | for i in range(len(selobj.SubObjects)): 86 | if isinstance(selobj.SubObjects[i], Part.Vertex): 87 | verts.append((selobj.Object, selobj.SubElementNames[i])) 88 | return len(verts) == 2 89 | else: 90 | return False 91 | 92 | def GetResources(self): 93 | return {'Pixmap': TOOL_ICON, 94 | 'MenuText': "Create a Parametric Line", 95 | 'ToolTip': "Create a Parametric Line from two Vertex

Select two vertex then run this command" 96 | } 97 | 98 | 99 | Gui.addCommand('FrameForge_ParametricLine', CreateParametricLineCommand()) 100 | -------------------------------------------------------------------------------- /freecad/frameforge/resources/icons/corner-coped-type.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 36 | 38 | 43 | 51 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /freecad/frameforge/resources/icons/corner-end-miter.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 36 | 38 | 43 | 47 | 51 | 55 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /freecad/frameforge/resources/icons/corner-end-trim.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 36 | 39 | 40 | 42 | 47 | 52 | 57 | 62 | 67 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /freecad/frameforge/resources/icons/corner-simple-type.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 36 | 38 | 43 | 51 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /freecad/frameforge/resources/icons/corner.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 36 | 38 | 43 | 46 | 50 | 56 | 62 | 68 | 69 | 73 | 79 | 85 | 91 | 92 | 98 | 104 | 110 | 111 | 112 | 113 | -------------------------------------------------------------------------------- /freecad/frameforge/resources/icons/discretize.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 20 | 22 | 24 | 28 | 32 | 33 | 40 | 41 | 77 | 79 | 80 | 82 | image/svg+xml 83 | 85 | 86 | 87 | 88 | 89 | 93 | 99 | 105 | 111 | 117 | 123 | 129 | 135 | 141 | 147 | 153 | 154 | 155 | -------------------------------------------------------------------------------- /freecad/frameforge/resources/icons/extendcurve.svg: -------------------------------------------------------------------------------- 1 | 2 | 12 | 14 | 16 | 20 | 24 | 25 | 26 | 28 | 29 | 31 | image/svg+xml 32 | 34 | 35 | 36 | 37 | 38 | 40 | 44 | 50 | 56 | 60 | 64 | 70 | 76 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /freecad/frameforge/resources/icons/joincurve.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 20 | 22 | 24 | 28 | 32 | 33 | 40 | 41 | 74 | 76 | 77 | 79 | image/svg+xml 80 | 82 | 83 | 84 | 85 | 86 | 90 | 96 | 102 | 108 | 114 | 120 | 126 | 132 | 138 | 144 | 145 | 146 | -------------------------------------------------------------------------------- /freecad/frameforge/resources/icons/line.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 20 | 22 | 24 | 28 | 32 | 33 | 40 | 41 | 74 | 76 | 77 | 79 | image/svg+xml 80 | 82 | 83 | 84 | 85 | 86 | 90 | 96 | 103 | 110 | 111 | 112 | -------------------------------------------------------------------------------- /freecad/frameforge/resources/icons/list-add.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 23 | 27 | 31 | 32 | 42 | 51 | 53 | 57 | 61 | 62 | 73 | 76 | 80 | 84 | 85 | 95 | 97 | 101 | 105 | 106 | 117 | 118 | 137 | 144 | 145 | 147 | 148 | 150 | image/svg+xml 151 | 153 | 154 | 155 | 156 | [wmayer] 157 | 158 | 159 | Arch_Add 160 | 2011-10-10 161 | http://www.freecadweb.org/wiki/index.php?title=Artwork 162 | 163 | 164 | FreeCAD 165 | 166 | 167 | FreeCAD/src/Mod/Arch/Resources/icons/Arch_Add.svg 168 | 169 | 170 | FreeCAD LGPL2+ 171 | 172 | 173 | https://www.gnu.org/copyleft/lesser.html 174 | 175 | 176 | [agryson] Alexander Gryson 177 | 178 | 179 | 180 | 181 | 182 | 186 | 196 | 199 | 205 | 211 | 212 | 213 | 214 | -------------------------------------------------------------------------------- /freecad/frameforge/resources/icons/list-remove.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 23 | 27 | 31 | 32 | 42 | 51 | 53 | 57 | 61 | 62 | 73 | 76 | 80 | 84 | 85 | 95 | 97 | 101 | 105 | 106 | 117 | 118 | 137 | 144 | 145 | 147 | 148 | 150 | image/svg+xml 151 | 153 | 154 | 155 | 156 | [wmayer] 157 | 158 | 159 | Arch_Remove 160 | 2011-10-10 161 | http://www.freecadweb.org/wiki/index.php?title=Artwork 162 | 163 | 164 | FreeCAD 165 | 166 | 167 | FreeCAD/src/Mod/Arch/Resources/icons/Arch_Remove.svg 168 | 169 | 170 | FreeCAD LGPL2+ 171 | 172 | 173 | https://www.gnu.org/copyleft/lesser.html 174 | 175 | 176 | [agryson] Alexander Gryson 177 | 178 | 179 | 180 | 181 | 182 | 186 | 196 | 199 | 205 | 211 | 212 | 213 | 214 | -------------------------------------------------------------------------------- /freecad/frameforge/resources/icons/metalwb.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 37 | 39 | 41 | 45 | 49 | 53 | 54 | 65 | 75 | 78 | 82 | 86 | 87 | 97 | 107 | 110 | 114 | 115 | 116 | 121 | 124 | 133 | 136 | 140 | 144 | 151 | 155 | 162 | 169 | 170 | 177 | 184 | 191 | 198 | 202 | 206 | 210 | 214 | 218 | 222 | 226 | 227 | 228 | 246 | 247 | 248 | 249 | -------------------------------------------------------------------------------- /freecad/frameforge/resources/icons/overlap.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 36 | 38 | 43 | 47 | 51 | 57 | 63 | 69 | 70 | 74 | 80 | 86 | 92 | 93 | 99 | 105 | 111 | 112 | 116 | 120 | 124 | 128 | 132 | 136 | 140 | 144 | 148 | 152 | 157 | 161 | 165 | 166 | 170 | 174 | 178 | 182 | 186 | 190 | 194 | 198 | 202 | 206 | 211 | 215 | 219 | 220 | 221 | 222 | -------------------------------------------------------------------------------- /freecad/frameforge/resources/icons/parts_list.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 | image/svg+xml 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | ... 60 | 61 | 62 | 63 | 64 | 65 | Parts 66 | 67 | 68 | 69 | List 70 | 71 | 72 | -------------------------------------------------------------------------------- /freecad/frameforge/resources/icons/splitcurve.svg: -------------------------------------------------------------------------------- 1 | 2 | 12 | 14 | 16 | 20 | 24 | 25 | 26 | 28 | 29 | 31 | image/svg+xml 32 | 34 | 35 | 36 | 37 | 38 | 40 | 44 | 48 | 54 | 60 | 66 | 72 | 78 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /freecad/frameforge/resources/icons/trim_extend.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 37 | 39 | 49 | 52 | 56 | 60 | 61 | 71 | 72 | 77 | 80 | 82 | 88 | 94 | 100 | 106 | 112 | 113 | 116 | 122 | 126 | 127 | 132 | 137 | 138 | 139 | 140 | -------------------------------------------------------------------------------- /freecad/frameforge/resources/icons/warehouse_profiles.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 36 | 38 | 43 | 47 | 53 | 60 | 66 | 74 | 78 | 82 | 88 | 96 | 97 | 98 | -------------------------------------------------------------------------------- /freecad/frameforge/resources/images/profiles/Metal/Equal_Leg_Angles.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukh/frameforge/c85108171a1b728e61d780cced3423aff41c6fa5/freecad/frameforge/resources/images/profiles/Metal/Equal_Leg_Angles.png -------------------------------------------------------------------------------- /freecad/frameforge/resources/images/profiles/Metal/Equal_Leg_Angles_Fillet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukh/frameforge/c85108171a1b728e61d780cced3423aff41c6fa5/freecad/frameforge/resources/images/profiles/Metal/Equal_Leg_Angles_Fillet.png -------------------------------------------------------------------------------- /freecad/frameforge/resources/images/profiles/Metal/Flat_Sections.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukh/frameforge/c85108171a1b728e61d780cced3423aff41c6fa5/freecad/frameforge/resources/images/profiles/Metal/Flat_Sections.png -------------------------------------------------------------------------------- /freecad/frameforge/resources/images/profiles/Metal/HEA.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukh/frameforge/c85108171a1b728e61d780cced3423aff41c6fa5/freecad/frameforge/resources/images/profiles/Metal/HEA.png -------------------------------------------------------------------------------- /freecad/frameforge/resources/images/profiles/Metal/HEA_Fillet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukh/frameforge/c85108171a1b728e61d780cced3423aff41c6fa5/freecad/frameforge/resources/images/profiles/Metal/HEA_Fillet.png -------------------------------------------------------------------------------- /freecad/frameforge/resources/images/profiles/Metal/HEB.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukh/frameforge/c85108171a1b728e61d780cced3423aff41c6fa5/freecad/frameforge/resources/images/profiles/Metal/HEB.png -------------------------------------------------------------------------------- /freecad/frameforge/resources/images/profiles/Metal/HEB_Fillet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukh/frameforge/c85108171a1b728e61d780cced3423aff41c6fa5/freecad/frameforge/resources/images/profiles/Metal/HEB_Fillet.png -------------------------------------------------------------------------------- /freecad/frameforge/resources/images/profiles/Metal/HEM.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukh/frameforge/c85108171a1b728e61d780cced3423aff41c6fa5/freecad/frameforge/resources/images/profiles/Metal/HEM.png -------------------------------------------------------------------------------- /freecad/frameforge/resources/images/profiles/Metal/HEM_Fillet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukh/frameforge/c85108171a1b728e61d780cced3423aff41c6fa5/freecad/frameforge/resources/images/profiles/Metal/HEM_Fillet.png -------------------------------------------------------------------------------- /freecad/frameforge/resources/images/profiles/Metal/IPE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukh/frameforge/c85108171a1b728e61d780cced3423aff41c6fa5/freecad/frameforge/resources/images/profiles/Metal/IPE.png -------------------------------------------------------------------------------- /freecad/frameforge/resources/images/profiles/Metal/IPE_Fillet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukh/frameforge/c85108171a1b728e61d780cced3423aff41c6fa5/freecad/frameforge/resources/images/profiles/Metal/IPE_Fillet.png -------------------------------------------------------------------------------- /freecad/frameforge/resources/images/profiles/Metal/IPN.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukh/frameforge/c85108171a1b728e61d780cced3423aff41c6fa5/freecad/frameforge/resources/images/profiles/Metal/IPN.png -------------------------------------------------------------------------------- /freecad/frameforge/resources/images/profiles/Metal/IPN_Fillet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukh/frameforge/c85108171a1b728e61d780cced3423aff41c6fa5/freecad/frameforge/resources/images/profiles/Metal/IPN_Fillet.png -------------------------------------------------------------------------------- /freecad/frameforge/resources/images/profiles/Metal/Pipe.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukh/frameforge/c85108171a1b728e61d780cced3423aff41c6fa5/freecad/frameforge/resources/images/profiles/Metal/Pipe.png -------------------------------------------------------------------------------- /freecad/frameforge/resources/images/profiles/Metal/Rectangular_Hollow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukh/frameforge/c85108171a1b728e61d780cced3423aff41c6fa5/freecad/frameforge/resources/images/profiles/Metal/Rectangular_Hollow.png -------------------------------------------------------------------------------- /freecad/frameforge/resources/images/profiles/Metal/Rectangular_Hollow_Fillet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukh/frameforge/c85108171a1b728e61d780cced3423aff41c6fa5/freecad/frameforge/resources/images/profiles/Metal/Rectangular_Hollow_Fillet.png -------------------------------------------------------------------------------- /freecad/frameforge/resources/images/profiles/Metal/Round_Bar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukh/frameforge/c85108171a1b728e61d780cced3423aff41c6fa5/freecad/frameforge/resources/images/profiles/Metal/Round_Bar.png -------------------------------------------------------------------------------- /freecad/frameforge/resources/images/profiles/Metal/Square.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukh/frameforge/c85108171a1b728e61d780cced3423aff41c6fa5/freecad/frameforge/resources/images/profiles/Metal/Square.png -------------------------------------------------------------------------------- /freecad/frameforge/resources/images/profiles/Metal/Square_Fillet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukh/frameforge/c85108171a1b728e61d780cced3423aff41c6fa5/freecad/frameforge/resources/images/profiles/Metal/Square_Fillet.png -------------------------------------------------------------------------------- /freecad/frameforge/resources/images/profiles/Metal/Square_Hollow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukh/frameforge/c85108171a1b728e61d780cced3423aff41c6fa5/freecad/frameforge/resources/images/profiles/Metal/Square_Hollow.png -------------------------------------------------------------------------------- /freecad/frameforge/resources/images/profiles/Metal/Square_Hollow_Fillet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukh/frameforge/c85108171a1b728e61d780cced3423aff41c6fa5/freecad/frameforge/resources/images/profiles/Metal/Square_Hollow_Fillet.png -------------------------------------------------------------------------------- /freecad/frameforge/resources/images/profiles/Metal/UPE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukh/frameforge/c85108171a1b728e61d780cced3423aff41c6fa5/freecad/frameforge/resources/images/profiles/Metal/UPE.png -------------------------------------------------------------------------------- /freecad/frameforge/resources/images/profiles/Metal/UPE_Fillet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukh/frameforge/c85108171a1b728e61d780cced3423aff41c6fa5/freecad/frameforge/resources/images/profiles/Metal/UPE_Fillet.png -------------------------------------------------------------------------------- /freecad/frameforge/resources/images/profiles/Metal/UPN.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukh/frameforge/c85108171a1b728e61d780cced3423aff41c6fa5/freecad/frameforge/resources/images/profiles/Metal/UPN.png -------------------------------------------------------------------------------- /freecad/frameforge/resources/images/profiles/Metal/UPN_Fillet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukh/frameforge/c85108171a1b728e61d780cced3423aff41c6fa5/freecad/frameforge/resources/images/profiles/Metal/UPN_Fillet.png -------------------------------------------------------------------------------- /freecad/frameforge/resources/images/profiles/Metal/Unequal_Leg_Angles.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukh/frameforge/c85108171a1b728e61d780cced3423aff41c6fa5/freecad/frameforge/resources/images/profiles/Metal/Unequal_Leg_Angles.png -------------------------------------------------------------------------------- /freecad/frameforge/resources/images/profiles/Metal/Unequal_Leg_Angles_Fillet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukh/frameforge/c85108171a1b728e61d780cced3423aff41c6fa5/freecad/frameforge/resources/images/profiles/Metal/Unequal_Leg_Angles_Fillet.png -------------------------------------------------------------------------------- /freecad/frameforge/resources/images/profiles/Warehouse.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukh/frameforge/c85108171a1b728e61d780cced3423aff41c6fa5/freecad/frameforge/resources/images/profiles/Warehouse.png -------------------------------------------------------------------------------- /freecad/frameforge/resources/ui/create_trimmed_profiles.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Dialog 4 | 5 | 6 | 7 | 0 8 | 0 9 | 439 10 | 920 11 | 12 | 13 | 14 | Dialog 15 | 16 | 17 | 18 | 19 | 20 | TrimType 21 | 22 | 23 | 24 | 25 | 26 | Coped Cut 27 | 28 | 29 | 30 | :/icon/icons/corner-simple-type.svg:/icon/icons/corner-simple-type.svg 31 | 32 | 33 | 34 | 32 35 | 32 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | Simple Cut 44 | 45 | 46 | 47 | :/icon/icons/corner-coped-type.svg:/icon/icons/corner-coped-type.svg 48 | 49 | 50 | 51 | 32 52 | 32 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | Trim Primitives 64 | 65 | 66 | 67 | 68 | 69 | Trimmed Object 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | Add 79 | 80 | 81 | 82 | :/icon/icons/list-add.svg:/icon/icons/list-add.svg 83 | 84 | 85 | 86 | 87 | 88 | 89 | ... 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | Trimming Boundaries: 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | Add 108 | 109 | 110 | 111 | :/icon/icons/list-add.svg:/icon/icons/list-add.svg 112 | 113 | 114 | 115 | 116 | 117 | 118 | Remove 119 | 120 | 121 | 122 | :/icon/icons/list-remove.svg:/icon/icons/list-remove.svg 123 | 124 | 125 | 126 | 127 | 128 | 129 | Qt::Horizontal 130 | 131 | 132 | 133 | 40 134 | 20 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | Qt::Vertical 151 | 152 | 153 | 154 | 20 155 | 40 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | -------------------------------------------------------------------------------- /freecad/frameforge/translate_utils.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf8 -*- 2 | 3 | #*************************************************************************** 4 | #* * 5 | #* Copyright (c) 2020 kbwbe * 6 | #* * 7 | #* * 8 | #* This program is free software; you can redistribute it and/or modify * 9 | #* it under the terms of the GNU Lesser General Public License (LGPL) * 10 | #* as published by the Free Software Foundation; either version 2 of * 11 | #* the License, or (at your option) any later version. * 12 | #* for detail see the LICENCE text file. * 13 | #* * 14 | #* This program 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 * 17 | #* GNU Library General Public License for more details. * 18 | #* * 19 | #* You should have received a copy of the GNU Library General Public * 20 | #* License along with this program; if not, write to the Free Software * 21 | #* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * 22 | #* USA * 23 | #* * 24 | #*************************************************************************** 25 | 26 | # Too many errors, commenting out for now 27 | # 22:35:17 During initialization the error "'NoneType' object has no attribute 'setObjectName'" occurred in freecad.cool_wb 28 | # 22:35:17 -------------------------------------------------------------------------------- 29 | # 22:35:17 Traceback (most recent call last): 30 | # File "", line 235, in InitApplications 31 | # File "/usr/lib/python3.10/importlib/__init__.py", line 126, in import_module 32 | # return _bootstrap._gcd_import(name[level:], package, level) 33 | # File "", line 1050, in _gcd_import 34 | # File "", line 1027, in _find_and_load 35 | # File "", line 1006, in _find_and_load_unlocked 36 | # File "", line 688, in _load_unlocked 37 | # File "", line 883, in exec_module 38 | # File "", line 241, in _call_with_frames_removed 39 | # File "/media/toddg/data1/repos/forked/freecad-source/build/Mod/CoolWB/freecad/cool_wb/init_gui.py", line 4, in 40 | # from freecad.cool_wb.TranslateUtils import translate 41 | # File "/usr/lib/python3/dist-packages/shiboken2/files.dir/shibokensupport/__feature__.py", line 142, in _import 42 | # return original_import(name, *args, **kwargs) 43 | # File "/media/toddg/data1/repos/forked/freecad-source/build/Mod/CoolWB/freecad/cool_wb/translate_utils.py", line 31, in 44 | # from DraftGui import translate 45 | # File "/usr/lib/python3/dist-packages/shiboken2/files.dir/shibokensupport/__feature__.py", line 142, in _import 46 | # return original_import(name, *args, **kwargs) 47 | # File "/media/toddg/data1/repos/forked/freecad-source/build/Mod/Draft/DraftGui.py", line 1805, in 48 | # FreeCADGui.draftToolBar = DraftToolBar() 49 | # File "/media/toddg/data1/repos/forked/freecad-source/build/Mod/Draft/DraftGui.py", line 209, in __init__ 50 | # self.tray.setObjectName("Draft tray") 51 | # AttributeError: 'NoneType' object has no attribute 'setObjectName' 52 | # 22:35:17 -------------------------------------------------------------------------------- 53 | # 54 | # import FreeCAD 55 | # 56 | # 57 | # if FreeCAD.GuiUp: 58 | # from PySide.QtCore import QT_TRANSLATE_NOOP 59 | # from DraftGui import translate 60 | # else: 61 | # def QT_TRANSLATE_NOOP(context, text): 62 | # return text 63 | # 64 | # def translate(context, text): 65 | # return text 66 | 67 | def QT_TRANSLATE_NOOP(context, text): 68 | return text 69 | 70 | def translate(context, text): 71 | return text 72 | 73 | -------------------------------------------------------------------------------- /freecad/frameforge/trimmed_profile.py: -------------------------------------------------------------------------------- 1 | import os, glob 2 | import math 3 | 4 | from PySide import QtCore, QtGui 5 | 6 | import FreeCADGui as Gui 7 | import FreeCAD as App 8 | 9 | import Part, ArchCommands 10 | import BOPTools.SplitAPI 11 | 12 | import freecad.frameforge 13 | 14 | from freecad.frameforge.translate_utils import translate 15 | from freecad.frameforge import PROFILESPATH, PROFILEIMAGES_PATH, ICONPATH, UIPATH 16 | 17 | 18 | class TrimmedProfile: 19 | def __init__(self, obj): 20 | obj.addProperty("App::PropertyLink","TrimmedBody","TrimmedProfile", translate("App::Property", "Body to be trimmed")).TrimmedBody = None 21 | obj.addProperty("App::PropertyLinkSubList","TrimmingBoundary","TrimmedProfile", translate("App::Property", "Bodies that define boundaries")).TrimmingBoundary = None 22 | 23 | obj.addProperty("App::PropertyEnumeration","TrimmedProfileType","TrimmedProfile", translate("App::Property", "TrimmedProfile Type")).TrimmedProfileType = ["End Trim", "End Miter"] 24 | obj.addProperty("App::PropertyEnumeration","CutType","TrimmedProfile", translate("App::Property", "Cut Type")).CutType = ["Coped cut", "Simple cut",] 25 | 26 | obj.Proxy = self 27 | 28 | def onChanged(self, fp, prop): 29 | pass 30 | 31 | def execute(self, fp): 32 | ''' Print a short message when doing a recomputation, this method is mandatory ''' 33 | App.Console.PrintMessage("Recompute {}\n".format(fp.Name)) 34 | #TODO: Put these methods in proper functions 35 | if fp.TrimmedBody is None: 36 | return 37 | if len(fp.TrimmingBoundary) == 0: 38 | return 39 | 40 | cut_shapes = [] 41 | 42 | if fp.TrimmedProfileType == "End Trim": 43 | if fp.CutType == "Coped cut": 44 | shapes = [x[0].Shape for x in fp.TrimmingBoundary] 45 | shps = BOPTools.SplitAPI.slice(fp.TrimmedBody.Shape, shapes, mode="Split") 46 | for solid in shps.Solids: 47 | x = fp.TrimmedBody.Shape.CenterOfGravity.x 48 | y = fp.TrimmedBody.Shape.CenterOfGravity.y 49 | z = fp.TrimmedBody.Shape.CenterOfGravity.z 50 | if not solid.BoundBox.isInside(x, y, z): 51 | cut_shapes.append(Part.Shape(solid)) 52 | 53 | elif fp.CutType == "Simple cut": 54 | cut_shape = Part.Shape() 55 | for link in fp.TrimmingBoundary: 56 | part = link[0] 57 | for sub in link[1] : 58 | face = part.getSubObject(sub) 59 | if isinstance(face.Surface, Part.Plane): 60 | shp = self.getOutsideCV(face, fp.TrimmedBody.Shape) 61 | cut_shapes.append(shp) 62 | 63 | elif fp.TrimmedProfileType == "End Miter": 64 | doc = App.activeDocument() 65 | precision = 0.001 66 | target1 = self.getTarget(fp.TrimmedBody) 67 | edge1 = doc.getObject(target1[0].Name).getSubObject(target1[1][0]) 68 | bounds_target = [] 69 | for bound in fp.TrimmingBoundary: 70 | bounds_target.append(self.getTarget(bound[0])) 71 | trimming_boundary_edges = [] 72 | for target in bounds_target: 73 | trimming_boundary_edges.append(doc.getObject(target[0].Name).getSubObject(target[1][0])) 74 | for edge2 in trimming_boundary_edges: 75 | end1 = edge1.Vertexes[-1].Point 76 | start1 = edge1.Vertexes[0].Point 77 | end2 = edge2.Vertexes[-1].Point 78 | start2 = edge2.Vertexes[0].Point 79 | vec1 = start1.sub(end1) 80 | vec2 = start2.sub(end2) 81 | 82 | angle = math.degrees(vec1.getAngle(vec2)) 83 | 84 | if end1.distanceToPoint(start2) < precision or start1.distanceToPoint(end2) < precision : 85 | angle = 180-angle 86 | 87 | bisect = angle / 2.0 88 | 89 | if start1.distanceToPoint(start2) < precision : 90 | p1 = start1 91 | p2 = end1 92 | p3 = end2 93 | elif start1.distanceToPoint(end2) < precision : 94 | p1 = start1 95 | p2 = end1 96 | p3 = start2 97 | elif end1.distanceToPoint(start2) < precision : 98 | p1 = end1 99 | p2 = start1 100 | p3 = end2 101 | elif end1.distanceToPoint(end2) < precision : 102 | p1 = end1 103 | p2 = start1 104 | p3 = start2 105 | 106 | normal = Part.Plane(p1, p2, p3).toShape().normalAt(0,0) 107 | cutplane = Part.makePlane(10, 10, p1, vec1, normal) 108 | cutplane.rotate(p1, normal, -90+bisect) 109 | cut_shapes.append(self.getOutsideCV(cutplane, fp.TrimmedBody.Shape)) 110 | 111 | if len(cut_shapes) > 0: 112 | cut_shape = Part.Shape(cut_shapes[0]) 113 | for sh in cut_shapes[1:]: 114 | cut_shape = cut_shape.fuse(sh) 115 | 116 | self.makeShape(fp, cut_shape) 117 | 118 | def getOutsideCV(self, cutplane, shape): 119 | cv = ArchCommands.getCutVolume(cutplane, shape, clip=False, depth=0.0) 120 | if cv[1].isInside(shape.CenterOfGravity, 0.001, False): 121 | cv = cv[2] 122 | else: 123 | cv = cv[1] 124 | return cv 125 | 126 | def makeShape(self, fp, cutshape): 127 | if not cutshape.isNull(): 128 | fp.Shape = fp.TrimmedBody.Shape.cut(cutshape) 129 | else: 130 | #TODO: Do something when cutshape is Null 131 | print("cut_shape is Null") 132 | 133 | def getTarget(self, link): 134 | while True: 135 | if hasattr(link, "Target" ): 136 | return link.Target 137 | elif hasattr(link, "TrimmedProfileType"): 138 | link = link.TrimmedBody 139 | 140 | 141 | class ViewProviderTrimmedProfile: 142 | def __init__(self, obj): 143 | ''' Set this object to the proxy object of the actual view provider ''' 144 | obj.Proxy = self 145 | 146 | def attach(self, vobj): 147 | ''' Setup the scene sub-graph of the view provider, this method is mandatory ''' 148 | self.ViewObject = vobj 149 | self.Object = vobj.Object 150 | return 151 | 152 | def updateData(self, fp, prop): 153 | ''' If a property of the handled feature has changed we have the chance to handle this here ''' 154 | #App.Console.PrintMessage("Change {} property: {}\n".format(str(fp), str(prop))) 155 | if prop == "TrimmedBody": 156 | if fp.TrimmedBody: 157 | self.ViewObject.ShapeColor = fp.TrimmedBody.ViewObject.ShapeColor 158 | return 159 | 160 | def getDisplayModes(self, obj): 161 | ''' Return a list of display modes. ''' 162 | modes=[] 163 | return modes 164 | 165 | def getDefaultDisplayMode(self): 166 | ''' Return the name of the default display mode. It must be defined in getDisplayModes. ''' 167 | return "FlatLines" 168 | 169 | def setDisplayMode(self, mode): 170 | ''' Map the display mode defined in attach with those defined in getDisplayModes. 171 | Since they have the same names nothing needs to be done. This method is optional. 172 | ''' 173 | return mode 174 | 175 | def claimChildren(self): 176 | childrens = [self.Object.TrimmedBody] 177 | if len(childrens) > 0: 178 | for child in childrens: 179 | if child: 180 | #if hasattr("ViewObject", child) 181 | child.ViewObject.Visibility = False 182 | return childrens 183 | 184 | def onChanged(self, vp, prop): 185 | ''' Print the name of the property that has changed ''' 186 | #App.Console.PrintMessage("Change {} property: {}\n".format(str(vp), str(prop))) 187 | pass 188 | 189 | def onDelete(self, fp, sub): 190 | if self.Object.TrimmedBody: 191 | self.Object.TrimmedBody.ViewObject.Visibility = True 192 | return True 193 | 194 | def getIcon(self): 195 | ''' Return the icon in XMP format which will appear in the tree view. This method is optional 196 | and if not defined a default icon is shown. 197 | ''' 198 | return """ 199 | /* XPM */ 200 | static char * corner_xpm[] = { 201 | "16 16 4 1", 202 | " c None", 203 | ". c #000000", 204 | "+ c #3465A4", 205 | "@ c #ED2B00", 206 | " .. ", 207 | " ..++.. ", 208 | " .....+++++. ", 209 | " ..@@@@@.+++... ", 210 | " .@.@@@@@.+.++. ", 211 | " .@@.@...@.+++. ", 212 | " .@@@.@@@@.+++. ", 213 | " .@@@.@@@@.+++. ", 214 | " ..@@.@@@@.... ", 215 | " .+.@.@@... ", 216 | " .++....++. ", 217 | " .+++.++++. ", 218 | " .+++.++++. ", 219 | " .++.++++. ", 220 | " .+.+... ", 221 | " ... "}; 222 | """ 223 | 224 | def __getstate__(self): 225 | ''' When saving the document this object gets stored using Python's cPickle module. 226 | Since we have some un-pickable here -- the Coin stuff -- we must define this method 227 | to return a tuple of all pickable objects or None. 228 | ''' 229 | return None 230 | 231 | def __setstate__(self,state): 232 | ''' When restoring the pickled object from document we have the chance to set some 233 | internals here. Since no data were pickled nothing needs to be done here. 234 | ''' 235 | return None 236 | 237 | def setEdit(self, vobj, mode): 238 | if mode != 0: 239 | return None 240 | 241 | taskd = freecad.frameforge.create_trimmed_profiles_tool.CreateTrimmedProfileTaskPanel(self.Object, mode="edition") 242 | Gui.Control.showDialog(taskd) 243 | return True 244 | 245 | def unsetEdit(self, vobj, mode): 246 | if mode != 0: 247 | return None 248 | 249 | Gui.Control.closeDialog() 250 | return True 251 | 252 | def edit(self): 253 | FreeCADGui.ActiveDocument.setEdit(self.Object, 0) 254 | -------------------------------------------------------------------------------- /freecad/frameforge/version.py: -------------------------------------------------------------------------------- 1 | __version__ = "0.1.3" -------------------------------------------------------------------------------- /package.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | FrameForge 5 | FrameForge is dedicated for creating Frames and Beams, and apply operations (miter cuts, trim cuts) on these profiles. 6 | 0.1.3 7 | 2025-03-10 8 | Vivien Henry 9 | LGPLv3 10 | https://github.com/lukh/frameforge.git 11 | https://github.com/lukh/frameforge.git/-/raw/main/README.md 12 | freecad/frameforge/resources/icons/metalwb.svg 13 | 14 | 15 | 16 | FrameForge 17 | freecad/frameforge 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /prof_extractor.py: -------------------------------------------------------------------------------- 1 | import os 2 | import json 3 | import re 4 | import pprint 5 | 6 | output_data = {} 7 | 8 | 9 | with open("Profiles.txt") as fd: 10 | family = None 11 | norm = None 12 | unit = None 13 | headers = None 14 | 15 | 16 | 17 | 18 | while True: 19 | line = fd.readline() 20 | 21 | if line == "": 22 | break 23 | 24 | elif line.startswith('#'): 25 | continue 26 | 27 | elif line == "\n": 28 | family = None 29 | norm = None 30 | unit = None 31 | headers = None 32 | 33 | elif line.startswith('*'): 34 | family = line.strip('*\n') 35 | norm = fd.readline().strip('*\n') 36 | unit = fd.readline().strip('*\n') 37 | headers = fd.readline().strip('*\n').split("/") 38 | 39 | output_data[family] = { 40 | "norm":norm, 41 | "unit":unit, 42 | "fillet":True, 43 | "sizes":{} 44 | } 45 | 46 | current_data = output_data[family] 47 | 48 | else: 49 | data_line = re.split(r'\t+', line.strip("\n").rstrip('\t')) 50 | data_line = [s.strip() for s in data_line] 51 | 52 | d = dict(zip(headers[1:], data_line[1:])) 53 | current_data['sizes'][data_line[0]] = d 54 | 55 | 56 | with open("metal.json", "w") as fd: 57 | json.dump(output_data, fd, indent=4) 58 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | import os 3 | 4 | version_path = os.path.join(os.path.abspath(os.path.dirname(__file__)), 5 | "freecad", "frameforge", "version.py") 6 | with open(version_path) as fp: 7 | exec(fp.read()) 8 | 9 | setup(name='frameforge', 10 | version=str(__version__), 11 | packages=['freecad', 12 | 'freecad.frameforge'], 13 | maintainer="Vivien HENRY", 14 | maintainer_email="vivien.henry@inductivebrain.fr", 15 | url="https://github.com/lukh/frameforge", 16 | description="FrameForge is dedicated for creating Frames and Beams, and apply operations (miter cuts, trim cuts) on these profiles.", 17 | include_package_data=True) 18 | --------------------------------------------------------------------------------