├── .github ├── ISSUE_TEMPLATE │ ├── bug-report.yml │ ├── config.yml │ └── feature-request.yml └── workflows │ └── scorecard.yml ├── .gitignore ├── ExtrudedCutout.py ├── InitGui.py ├── LICENSE ├── Macros └── SheetMetalUnfoldUpdater.FCMacro ├── README.md ├── Resources ├── SheetMetal.qrc ├── SheetMetal4.gif ├── icons │ ├── BaseShape_BC.svg │ ├── BaseShape_BL.svg │ ├── BaseShape_BR.svg │ ├── BaseShape_CC.svg │ ├── BaseShape_CL.svg │ ├── BaseShape_CR.svg │ ├── BaseShape_Sel.svg │ ├── BaseShape_TC.svg │ ├── BaseShape_TL.svg │ ├── BaseShape_TR.svg │ ├── FaceSelection_Off.svg │ ├── FaceSelection_On.svg │ ├── Invert_Off.svg │ ├── Invert_On.svg │ ├── SMLogo.svg │ ├── SheetMetal_AddBase.svg │ ├── SheetMetal_AddBaseShape.svg │ ├── SheetMetal_AddBase_alt.svg │ ├── SheetMetal_AddBend.svg │ ├── SheetMetal_AddCornerRelief.svg │ ├── SheetMetal_AddCutout.svg │ ├── SheetMetal_AddFoldWall.svg │ ├── SheetMetal_AddJunction.svg │ ├── SheetMetal_AddRelief.svg │ ├── SheetMetal_AddRelief_alt.svg │ ├── SheetMetal_AddWall.svg │ ├── SheetMetal_CornerRelief_Circle.svg │ ├── SheetMetal_CornerRelief_Circle_alt.svg │ ├── SheetMetal_CornerRelief_Square.svg │ ├── SheetMetal_CornerRelief_Square_alt.svg │ ├── SheetMetal_Extrude.svg │ ├── SheetMetal_Forming.svg │ ├── SheetMetal_Refold.svg │ ├── SheetMetal_SketchOnSheet.svg │ ├── SheetMetal_Unfold.svg │ ├── SheetMetal_UnfoldExport.svg │ ├── SheetMetal_UnfoldUnattended.svg │ ├── SheetMetal_UnfoldUpdate.svg │ ├── SheetMetal_Update.svg │ ├── SheetMetal_WallLenInn.svg │ ├── SheetMetal_WallLenLeg.svg │ ├── SheetMetal_WallLenOut.svg │ ├── SheetMetal_WallLenTang.svg │ ├── SheetMetal_WallPosMatIns.svg │ ├── SheetMetal_WallPosMatOut.svg │ ├── SheetMetal_WallPosOffset.svg │ ├── SheetMetal_WallPosThkOut.svg │ └── preferences-sheetmetal.svg ├── panels │ ├── AddJunctionPanel.ui │ ├── BaseShapeOptions.ui │ ├── BendCornerPanel.ui │ ├── BendOnLinePanel.ui │ ├── CornerReliefPanel.ui │ ├── CreateBaseShape.ui │ ├── ExtendTaskPanel.ui │ ├── ExtrudedCutoutPanel.ui │ ├── FlangeAdvancedParameters.ui │ ├── FlangeParameters.ui │ ├── SMprefs.ui │ ├── SolidCornerReliefPanel.ui │ ├── StampPanel.ui │ ├── UnfoldOptions.ui │ ├── UnfoldSketchOptions.ui │ └── WrappedCutoutPanel.ui ├── sheetmetal_terms.png ├── smvideo.jpg └── translations │ ├── README.md │ ├── SheetMetal.ts │ ├── SheetMetal_de.qm │ ├── SheetMetal_de.ts │ ├── SheetMetal_el.qm │ ├── SheetMetal_el.ts │ ├── SheetMetal_es-ar.qm │ ├── SheetMetal_es-ar.ts │ ├── SheetMetal_es-es.qm │ ├── SheetMetal_es-es.ts │ ├── SheetMetal_fr.qm │ ├── SheetMetal_fr.ts │ ├── SheetMetal_it.qm │ ├── SheetMetal_it.ts │ ├── SheetMetal_pl.qm │ ├── SheetMetal_pl.ts │ ├── SheetMetal_pt-br.qm │ ├── SheetMetal_pt-br.ts │ ├── SheetMetal_pt-pt.qm │ ├── SheetMetal_pt-pt.ts │ ├── SheetMetal_sr-cs.qm │ ├── SheetMetal_sr-cs.ts │ ├── SheetMetal_sr.qm │ ├── SheetMetal_sr.ts │ ├── SheetMetal_zh-tw.qm │ ├── SheetMetal_zh-tw.ts │ └── update_translation.sh ├── SMTests ├── __init__.py ├── testFolder.py └── testKfactor.py ├── SheetMetalBaseCmd.py ├── SheetMetalBaseShapeCmd.py ├── SheetMetalBend.py ├── SheetMetalBendSolid.py ├── SheetMetalCmd.py ├── SheetMetalCornerReliefCmd.py ├── SheetMetalExtendCmd.py ├── SheetMetalFoldCmd.py ├── SheetMetalFormingCmd.py ├── SheetMetalJunction.py ├── SheetMetalKfactor.py ├── SheetMetalLogger.py ├── SheetMetalNewUnfolder.py ├── SheetMetalRelief.py ├── SheetMetalTools.py ├── SheetMetalUnfoldCmd.py ├── SheetMetalUnfolder.py ├── SketchOnSheetMetalCmd.py ├── TestSheetMetal.py ├── engineering_mode.py ├── lookup.py ├── package.xml ├── smwb_locator.py ├── tools ├── Press_brake_schematic.svg ├── README.md ├── air-bending-punch-distances.svg ├── calc-unfold.py └── terminology.png └── updating-ui.md /.github/ISSUE_TEMPLATE/bug-report.yml: -------------------------------------------------------------------------------- 1 | name: Bug Report 2 | description: Report a bug 3 | labels: ["bug"] 4 | assignees: ["shaise"] 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | Please fill out the sections below to help everyone identify and fix the bug 10 | IMPORTANT: If possible, add a freecad file that demonstrates the bug 11 | - type: textarea 12 | id: description 13 | attributes: 14 | label: Describe your issue 15 | placeholder: | 16 | When I use the command happens 17 | Here are the steps to reproduce it: 18 | 1 .. 19 | 2 .. 20 | description: Describe the problem and how it impacts user experience, workflow, maintainability or speed of the code. If the problem appears to be a bug with the current functionality, provide as test case or recipe that reproduces the error. Ideally record a macro and attach it. 21 | validations: 22 | required: true 23 | - type: textarea 24 | id: full_version 25 | attributes: 26 | label: FreeCAD version info + SheetMetal WB version 27 | placeholder: | 28 | OS: 29 | Architecture: x86_64 30 | Version: 31 | Build type: Release 32 | Python 3.11.10, Qt 5.15.15, Coin 4.0.3, Vtk 9.3.0, OCC 7.8.1 33 | Locale: English/United States (en_US) 34 | Stylesheet/Theme/QtStyle: OpenLight.qss/OpenLight/Fusion 35 | Installed mods: 36 | * sheetmetal 0.5.5 37 | * ... 38 | description: | 39 | Please use the About FreeCAD dialog to copy your full version information and paste it here, include the SheetMetal WB version on it. 40 | (At the bottom left of the about dialog there is a `Copy to clipboard` button) 41 | render: shell 42 | - type: textarea 43 | id: screenshots 44 | attributes: 45 | label: Put here any screenshots or videos (optional) 46 | description: | 47 | Add links, references, screenshots or anything that will give us more context about the issue you are encountering! 48 | If there is a discussion about the problem on the forum, provide link(s) here. 49 | 50 | Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in. To attach a FCStd file, ZIP it first (GitHub won't recognize the extension otherwise). 51 | - type: markdown 52 | attributes: 53 | value: | 54 | Thanks for reporting this issue! We will get back to you as soon as possible. 55 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: true 2 | contact_links: 3 | - name: General questions about the workbench 4 | url: https://forum.freecad.org/viewtopic.php?t=60818 5 | about: You can join the discussions on the FreeCAD forum. 6 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-request.yml: -------------------------------------------------------------------------------- 1 | name: New feature 2 | description: Suggest or request a new feature 3 | labels: ["enhancement"] 4 | assignees: ["shaise"] 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | Please fill out the sections below to properly describe the new feature you are suggesting. 10 | - type: textarea 11 | id: description 12 | attributes: 13 | label: Describe the feature 14 | placeholder: A new commands to generate this new SheetMetal feature 15 | validations: 16 | required: true 17 | - type: textarea 18 | id: context 19 | attributes: 20 | label: Additional context 21 | placeholder: | 22 | Add any other context or screenshots about the feature request here. 23 | - type: dropdown 24 | id: assign 25 | attributes: 26 | label: "Would you like to work on this issue?" 27 | options: 28 | - "Yes" 29 | - type: markdown 30 | attributes: 31 | value: | 32 | Thanks for your suggestion! Let's see together if it can be implemented. 33 | -------------------------------------------------------------------------------- /.github/workflows/scorecard.yml: -------------------------------------------------------------------------------- 1 | # This workflow uses actions that are not certified by GitHub. They are provided 2 | # by a third-party and are governed by separate terms of service, privacy 3 | # policy, and support documentation. 4 | 5 | name: Scorecard supply-chain security 6 | on: 7 | # For Branch-Protection check. Only the default branch is supported. See 8 | # https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection 9 | branch_protection_rule: 10 | # To guarantee Maintained check is occasionally updated. See 11 | # https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained 12 | schedule: 13 | - cron: '26 23 * * 6' 14 | push: 15 | branches: [ "master" ] 16 | 17 | # Declare default permissions as read only. 18 | permissions: read-all 19 | 20 | jobs: 21 | analysis: 22 | name: Scorecard analysis 23 | runs-on: ubuntu-latest 24 | permissions: 25 | # Needed to upload the results to code-scanning dashboard. 26 | security-events: write 27 | # Needed to publish results and get a badge (see publish_results below). 28 | id-token: write 29 | # Uncomment the permissions below if installing in a private repository. 30 | # contents: read 31 | # actions: read 32 | 33 | steps: 34 | - name: "Checkout code" 35 | uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 36 | with: 37 | persist-credentials: false 38 | 39 | - name: "Run analysis" 40 | uses: ossf/scorecard-action@0864cf19026789058feabb7e87baa5f140aac736 # v2.3.1 41 | with: 42 | results_file: results.sarif 43 | results_format: sarif 44 | # (Optional) "write" PAT token. Uncomment the `repo_token` line below if: 45 | # - you want to enable the Branch-Protection check on a *public* repository, or 46 | # - you are installing Scorecard on a *private* repository 47 | # To create the PAT, follow the steps in https://github.com/ossf/scorecard-action?tab=readme-ov-file#authentication-with-fine-grained-pat-optional. 48 | # repo_token: ${{ secrets.SCORECARD_TOKEN }} 49 | 50 | # Public repositories: 51 | # - Publish results to OpenSSF REST API for easy access by consumers 52 | # - Allows the repository to include the Scorecard badge. 53 | # - See https://github.com/ossf/scorecard-action#publishing-results. 54 | # For private repositories: 55 | # - `publish_results` will always be set to `false`, regardless 56 | # of the value entered here. 57 | publish_results: true 58 | 59 | # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF 60 | # format to the repository Actions tab. 61 | - name: "Upload artifact" 62 | uses: actions/upload-artifact@v4 # v3.pre.node20 63 | with: 64 | name: sarif_file 65 | path: results.sarif 66 | retention-days: 5 67 | 68 | # Upload the results to GitHub's code scanning dashboard (optional). 69 | # Commenting out will disable upload of results to your repo's Code Scanning dashboard 70 | - name: "Upload to code-scanning" 71 | uses: github/codeql-action/upload-sarif@v3 72 | with: 73 | sarif_file: results.sarif 74 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | /SheetMetal2.py -------------------------------------------------------------------------------- /InitGui.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | ############################################################################## 3 | # 4 | # InitGui.py 5 | # 6 | # Copyright 2015 Shai Seger 7 | # 8 | # This program is free software; you can redistribute it and/or 9 | # modify it under the terms of the GNU Lesser General Public 10 | # License as published by the Free Software Foundation; either 11 | # version 2 of the License, or (at your option) any later version. 12 | # 13 | # This program is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU Lesser General Public 19 | # License along with this program; if not, write to the Free Software 20 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 21 | # MA 02110-1301, USA. 22 | # 23 | # 24 | ############################################################################## 25 | 26 | import os 27 | import FreeCAD 28 | from FreeCAD import Gui 29 | import SheetMetalTools 30 | from engineering_mode import engineering_mode_enabled 31 | 32 | # add translations path 33 | SMWBPath = SheetMetalTools.mod_path 34 | SMIconPath = SheetMetalTools.icons_path 35 | 36 | LanguagePath = os.path.join(SMWBPath, "Resources", "translations") 37 | Gui.addLanguagePath(LanguagePath) 38 | Gui.updateLocale() 39 | 40 | 41 | class SMWorkbench(Workbench): 42 | global SMIconPath 43 | global SMWBPath 44 | global SHEETMETALWB_VERSION 45 | global engineering_mode_enabled 46 | 47 | MenuText = FreeCAD.Qt.translate("SheetMetal", "Sheet Metal") 48 | ToolTip = FreeCAD.Qt.translate( 49 | "SheetMetal", 50 | "Sheet Metal workbench allows for designing and unfolding sheet metal parts", 51 | ) 52 | Icon = os.path.join(SMIconPath, "SMLogo.svg") 53 | 54 | def Initialize(self): 55 | "This function is executed when FreeCAD starts" 56 | import SheetMetalCmd # import here all the needed files that create your FreeCAD commands 57 | import SheetMetalExtendCmd 58 | import SheetMetalUnfolder 59 | import SheetMetalBaseCmd 60 | import SheetMetalFoldCmd 61 | import SheetMetalRelief 62 | import SheetMetalJunction 63 | import SheetMetalBend 64 | import SketchOnSheetMetalCmd 65 | import ExtrudedCutout 66 | import SheetMetalCornerReliefCmd 67 | import SheetMetalFormingCmd 68 | import SheetMetalUnfoldCmd 69 | import SheetMetalBaseShapeCmd 70 | import os.path 71 | 72 | self.list = [ 73 | "SheetMetal_AddBase", 74 | "SheetMetal_AddWall", 75 | "SheetMetal_Extrude", 76 | "SheetMetal_AddFoldWall", 77 | "SheetMetal_Unfold", 78 | "SheetMetal_UnfoldUpdate", 79 | "SheetMetal_AddCornerRelief", 80 | "SheetMetal_AddRelief", 81 | "SheetMetal_AddJunction", 82 | "SheetMetal_AddBend", 83 | "SheetMetal_SketchOnSheet", 84 | "SheetMetal_AddCutout", 85 | "SheetMetal_Forming", 86 | "SheetMetal_BaseShape", 87 | ] # A list of command names created in the line above 88 | if engineering_mode_enabled(): 89 | self.list.insert( 90 | self.list.index("SheetMetal_Unfold") + 1, "SheetMetal_UnattendedUnfold" 91 | ) 92 | self.appendToolbar( 93 | FreeCAD.Qt.translate("SheetMetal", "Sheet Metal"), self.list 94 | ) # creates a new toolbar with your commands 95 | self.appendMenu( 96 | FreeCAD.Qt.translate("SheetMetal", "&Sheet Metal"), self.list 97 | ) # creates a new menu 98 | # self.appendMenu(["An existing Menu","My submenu"],self.list) # appends a submenu to an existing menu 99 | Gui.addPreferencePage(os.path.join(SMWBPath, "Resources/panels/SMprefs.ui"), "SheetMetal") 100 | Gui.addIconPath(SMIconPath) 101 | 102 | def Activated(self): 103 | "This function is executed when the workbench is activated" 104 | return 105 | 106 | def Deactivated(self): 107 | "This function is executed when the workbench is deactivated" 108 | return 109 | 110 | def ContextMenu(self, recipient): 111 | "This is executed whenever the user right-clicks on screen" 112 | # "recipient" will be either "view" or "tree" 113 | self.appendContextMenu( 114 | FreeCAD.Qt.translate("SheetMetal", "Sheet Metal"), self.list 115 | ) # add commands to the context menu 116 | 117 | def GetClassName(self): 118 | # this function is mandatory if this is a full python workbench 119 | return "Gui::PythonWorkbench" 120 | 121 | 122 | Gui.addWorkbench(SMWorkbench()) 123 | -------------------------------------------------------------------------------- /Resources/SheetMetal.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | icons/SheetMetal_AddBase.svg 4 | icons/SheetMetal_AddBend.svg 5 | icons/SheetMetal_AddCornerRelief.svg 6 | icons/SheetMetal_AddFoldWall.svg 7 | icons/SheetMetal_AddJunction.svg 8 | icons/SheetMetal_AddRelief.svg 9 | icons/SheetMetal_AddWall.svg 10 | icons/SheetMetal_Extrude.svg 11 | icons/SheetMetal_Forming.svg 12 | icons/SheetMetal_SketchOnSheet.svg 13 | icons/SheetMetal_AddCutout.svg 14 | icons/SheetMetal_Unfold.svg 15 | icons/SheetMetal_UnfoldUnattended.svg 16 | icons/SheetMetal_UnfoldUpdate.svg 17 | icons/SheetMetal_Update.svg 18 | icons/preferences-sheetmetal.svg 19 | icons/SMLogo.svg 20 | 21 | 22 | -------------------------------------------------------------------------------- /Resources/SheetMetal4.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaise/FreeCAD_SheetMetal/21ee403ab369cc2d08de0dcc989cb1bfd201773d/Resources/SheetMetal4.gif -------------------------------------------------------------------------------- /Resources/icons/BaseShape_BC.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 213 | -------------------------------------------------------------------------------- /Resources/icons/BaseShape_BL.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 165 | -------------------------------------------------------------------------------- /Resources/icons/BaseShape_BR.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 161 | -------------------------------------------------------------------------------- /Resources/icons/BaseShape_CC.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 223 | -------------------------------------------------------------------------------- /Resources/icons/BaseShape_CL.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 149 | -------------------------------------------------------------------------------- /Resources/icons/BaseShape_CR.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 219 | -------------------------------------------------------------------------------- /Resources/icons/BaseShape_Sel.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 57 | -------------------------------------------------------------------------------- /Resources/icons/BaseShape_TC.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 225 | -------------------------------------------------------------------------------- /Resources/icons/BaseShape_TL.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 174 | -------------------------------------------------------------------------------- /Resources/icons/BaseShape_TR.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 167 | -------------------------------------------------------------------------------- /Resources/icons/FaceSelection_On.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 15 | 17 | 19 | 23 | 27 | 28 | 30 | 34 | 38 | 39 | 41 | 45 | 49 | 50 | 52 | 56 | 60 | 61 | 71 | 81 | 91 | 100 | 102 | 106 | 110 | 111 | 119 | 121 | 125 | 129 | 130 | 138 | 147 | 149 | 153 | 157 | 158 | 159 | 161 | 162 | 164 | image/svg+xml 165 | 167 | 168 | 169 | 170 | 173 | 177 | 181 | 185 | 189 | 193 | 197 | 201 | 205 | 209 | 213 | 214 | 218 | 219 | -------------------------------------------------------------------------------- /Resources/panels/AddJunctionPanel.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | SMAddJunctionTaskPanel 4 | 5 | 6 | 7 | 0 8 | 0 9 | 279 10 | 369 11 | 12 | 13 | 14 | Junction properties 15 | 16 | 17 | 18 | 19 | 20 | 0 21 | 22 | 23 | 24 | 25 | Select 26 | 27 | 28 | true 29 | 30 | 31 | false 32 | 33 | 34 | 35 | 36 | 37 | 38 | Clear selection 39 | 40 | 41 | 42 | 43 | 44 | 45 | Qt::Horizontal 46 | 47 | 48 | 49 | 40 50 | 20 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 0 62 | 0 63 | 64 | 65 | 66 | true 67 | 68 | 69 | 70 | Name 71 | 72 | 73 | 74 | 75 | SubElement 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | Dimensions 84 | 85 | 86 | 87 | 88 | 89 | Qt::Vertical 90 | 91 | 92 | 93 | 20 94 | 40 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | Width 103 | 104 | 105 | 106 | 107 | 108 | 109 | mm 110 | 111 | 112 | 0.000000000000000 113 | 114 | 115 | 116 | 117 | 118 | 119 | false 120 | 121 | 122 | Refine 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | Gui::QuantitySpinBox 134 | QWidget 135 |
Gui/QuantitySpinBox.h
136 |
137 |
138 | 139 | tree 140 | JunctionWidth 141 | 142 | 143 | 144 |
145 | -------------------------------------------------------------------------------- /Resources/panels/BendCornerPanel.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | SMBendCornerTaskPanel 4 | 5 | 6 | 7 | 0 8 | 0 9 | 259 10 | 369 11 | 12 | 13 | 14 | Bend sharp corner properties 15 | 16 | 17 | 18 | 19 | 20 | 0 21 | 22 | 23 | 24 | 25 | Select 26 | 27 | 28 | true 29 | 30 | 31 | false 32 | 33 | 34 | 35 | 36 | 37 | 38 | Clear selection 39 | 40 | 41 | 42 | 43 | 44 | 45 | Qt::Horizontal 46 | 47 | 48 | 49 | 40 50 | 20 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 0 62 | 0 63 | 64 | 65 | 66 | true 67 | 68 | 69 | 70 | Name 71 | 72 | 73 | 74 | 75 | SubElement 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | Extend 84 | 85 | 86 | 87 | 88 | 89 | Qt::Vertical 90 | 91 | 92 | 93 | 20 94 | 40 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | Radius 103 | 104 | 105 | 106 | 107 | 108 | 109 | mm 110 | 111 | 112 | 0.000000000000000 113 | 114 | 115 | 116 | 117 | 118 | 119 | false 120 | 121 | 122 | Refine 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | Gui::QuantitySpinBox 134 | QWidget 135 |
Gui/QuantitySpinBox.h
136 |
137 |
138 | 139 | tree 140 | Radius 141 | 142 | 143 | 144 |
145 | -------------------------------------------------------------------------------- /Resources/panels/BendOnLinePanel.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | SMBendOnLineTaskPanel 4 | 5 | 6 | 7 | 0 8 | 0 9 | 291 10 | 565 11 | 12 | 13 | 14 | Fold on sketch line properties 15 | 16 | 17 | 18 | 19 | 20 | QFrame::StyledPanel 21 | 22 | 23 | QFrame::Raised 24 | 25 | 26 | 27 | 28 | 29 | true 30 | 31 | 32 | 33 | 34 | 35 | 36 | true 37 | 38 | 39 | 40 | 41 | 42 | 43 | Base Object 44 | 45 | 46 | true 47 | 48 | 49 | 50 | 51 | 52 | 53 | Bend Line 54 | 55 | 56 | true 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | Position 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 0 77 | 0 78 | 79 | 80 | 81 | 82 | Intersection of Planes 83 | 84 | 85 | 86 | 87 | Middle 88 | 89 | 90 | 91 | 92 | Backward 93 | 94 | 95 | 96 | 97 | Forward 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | Flip Direction 110 | 111 | 112 | 113 | 114 | 115 | 116 | Unbend 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | Bend Radius 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | Bend Angle 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | Qt::Vertical 152 | 153 | 154 | 155 | 20 156 | 40 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | Gui::QuantitySpinBox 166 | QWidget 167 |
Gui/QuantitySpinBox.h
168 |
169 |
170 | 171 | 172 |
173 | -------------------------------------------------------------------------------- /Resources/panels/CreateBaseShape.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | SMCreateBaseShapeTaskPanel 4 | 5 | 6 | 7 | 0 8 | 0 9 | 285 10 | 546 11 | 12 | 13 | 14 | 15 | 0 16 | 0 17 | 18 | 19 | 20 | Sketched base shape properties 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | Sketch 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 0 42 | 0 43 | 44 | 45 | 46 | Bend Options 47 | 48 | 49 | 50 | 51 | 52 | Bend Plane 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 0 61 | 0 62 | 63 | 64 | 65 | 0.000001000000000 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 0 74 | 0 75 | 76 | 77 | 78 | 0.000001000000000 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 0 87 | 0 88 | 89 | 90 | 91 | Radius 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 0 100 | 0 101 | 102 | 103 | 104 | Thickness 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 0 113 | 0 114 | 115 | 116 | 117 | 118 | Outside 119 | 120 | 121 | 122 | 123 | Inside 124 | 125 | 126 | 127 | 128 | Centered 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 0 141 | 0 142 | 143 | 144 | 145 | Extrude options 146 | 147 | 148 | 149 | 150 | 151 | 152 | 0 153 | 0 154 | 155 | 156 | 157 | Length 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 0 166 | 0 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | Create the base shape as a new body 175 | 176 | 177 | Symmetric 178 | 179 | 180 | 181 | 182 | 183 | 184 | Extend sides and flange to close all gaps 185 | 186 | 187 | Reverse Direction 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | Qt::Vertical 198 | 199 | 200 | 201 | 20 202 | 40 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | Gui::QuantitySpinBox 212 | QWidget 213 |
Gui/QuantitySpinBox.h
214 |
215 |
216 | 217 | 218 |
219 | -------------------------------------------------------------------------------- /Resources/panels/ExtendTaskPanel.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | SMExtendTaskPanel 4 | 5 | 6 | 7 | 0 8 | 0 9 | 259 10 | 369 11 | 12 | 13 | 14 | Extend properties 15 | 16 | 17 | 18 | 19 | 20 | 0 21 | 22 | 23 | 24 | 25 | Select 26 | 27 | 28 | true 29 | 30 | 31 | false 32 | 33 | 34 | 35 | 36 | 37 | 38 | Clear selection 39 | 40 | 41 | 42 | 43 | 44 | 45 | Qt::Horizontal 46 | 47 | 48 | 49 | 40 50 | 20 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 0 62 | 0 63 | 64 | 65 | 66 | true 67 | 68 | 69 | 70 | Name 71 | 72 | 73 | 74 | 75 | SubElement 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | Extend 84 | 85 | 86 | 87 | 88 | 89 | mm 90 | 91 | 92 | 93 | 94 | 95 | 96 | Qt::Vertical 97 | 98 | 99 | 100 | 20 101 | 40 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | mm 110 | 111 | 112 | 0.000000000000000 113 | 114 | 115 | 116 | 117 | 118 | 119 | Length 120 | 121 | 122 | 123 | 124 | 125 | 126 | Offset A 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 0 135 | 0 136 | 137 | 138 | 139 | Offset B 140 | 141 | 142 | 143 | 144 | 145 | 146 | mm 147 | 148 | 149 | 150 | 151 | 152 | 153 | Refine 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | Gui::QuantitySpinBox 165 | QWidget 166 |
Gui/QuantitySpinBox.h
167 |
168 |
169 | 170 | tree 171 | Length 172 | OffsetA 173 | OffsetB 174 | 175 | 176 | 177 |
178 | -------------------------------------------------------------------------------- /Resources/panels/FlangeAdvancedParameters.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | SMFlangeAdvancedTaskPanel 4 | 5 | 6 | 7 | 0 8 | 0 9 | 260 10 | 437 11 | 12 | 13 | 14 | Advanced Parameters 15 | 16 | 17 | 18 | 19 | 20 | Relief Cuts 21 | 22 | 23 | 24 | 25 | 26 | Width 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 0 35 | 0 36 | 37 | 38 | 39 | Depth 40 | 41 | 42 | 43 | 44 | 45 | 46 | mm 47 | 48 | 49 | 0.000000000000000 50 | 51 | 52 | 53 | 54 | 55 | 56 | mm 57 | 58 | 59 | 0.000000000000000 60 | 61 | 62 | 63 | 64 | 65 | 66 | 1 67 | 68 | 69 | 70 | 71 | Rectangle 72 | 73 | 74 | true 75 | 76 | 77 | reliefTypeButtonGroup 78 | 79 | 80 | 81 | 82 | 83 | 84 | Round 85 | 86 | 87 | reliefTypeButtonGroup 88 | 89 | 90 | 91 | 92 | 93 | 94 | Qt::Horizontal 95 | 96 | 97 | 98 | 40 99 | 20 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | Auto Miter 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | mm 126 | 127 | 128 | 0.000000000000000 129 | 130 | 131 | 132 | 133 | 134 | 135 | Maximum Extend Distance 136 | 137 | 138 | 139 | 140 | 141 | 142 | mm 143 | 144 | 145 | 0.000000000000000 146 | 147 | 148 | 149 | 150 | 151 | 152 | Minimum Gap 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | Manual Miter 163 | 164 | 165 | 166 | 167 | 168 | deg 169 | 170 | 171 | -360.000000000000000 172 | 173 | 174 | 360.000000000000000 175 | 176 | 177 | 178 | 179 | 180 | 181 | deg 182 | 183 | 184 | -360.000000000000000 185 | 186 | 187 | 360.000000000000000 188 | 189 | 190 | 191 | 192 | 193 | 194 | Angle 2 195 | 196 | 197 | 198 | 199 | 200 | 201 | Angle 1 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | Gui::QuantitySpinBox 213 | QWidget 214 |
Gui/QuantitySpinBox.h
215 |
216 |
217 | 218 | reliefRectangle 219 | reliefRound 220 | reliefWidth 221 | reliefDepth 222 | autoMiterCheckbox 223 | minGap 224 | maxExDist 225 | miterAngle1 226 | miterAngle2 227 | 228 | 229 | 230 | 231 | 232 | 233 |
234 | -------------------------------------------------------------------------------- /Resources/panels/SMprefs.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Gui::Dialog::DlgSettingsSheetMetal 4 | 5 | 6 | 7 | 0 8 | 0 9 | 563 10 | 401 11 | 12 | 13 | 14 | General settings 15 | 16 | 17 | 18 | 19 | 20 | 21 | 0 22 | 1 23 | 24 | 25 | 26 | General 27 | 28 | 29 | 30 | 31 | 32 | 0 33 | 34 | 35 | 36 | 37 | Engineering UX Mode 38 | 39 | 40 | 41 | 42 | 43 | 44 | Qt::Horizontal 45 | 46 | 47 | 48 | 40 49 | 20 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | true 58 | 59 | 60 | 0 61 | 62 | 63 | EngineeringUXMode 64 | 65 | 66 | Mod/SheetMetal 67 | 68 | 69 | 70 | Disabled 71 | 72 | 73 | 74 | 75 | Enabled 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 0 86 | 87 | 88 | 89 | 90 | Auto Link Bend Radius 91 | 92 | 93 | 94 | 95 | 96 | 97 | Qt::Horizontal 98 | 99 | 100 | 101 | 40 102 | 20 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | true 111 | 112 | 113 | 0 114 | 115 | 116 | AutoLinkBendRadius 117 | 118 | 119 | Mod/SheetMetal 120 | 121 | 122 | 123 | Disabled 124 | 125 | 126 | 127 | 128 | Enabled 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 0 139 | 140 | 141 | 142 | 143 | Qt::LeftToRight 144 | 145 | 146 | Revert To Old Unfolder 147 | 148 | 149 | UseOldUnfolder 150 | 151 | 152 | Mod/SheetMetal 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | Qt::Vertical 162 | 163 | 164 | 165 | 20 166 | 40 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 0 179 | 0 180 | 181 | 182 | 183 | 184 | 16777215 185 | 30 186 | 187 | 188 | 189 | 190 | 14 191 | 192 | 193 | 194 | Preferences for the SheetMetal Workbench 195 | 196 | 197 | 198 | 199 | 200 | 201 | qPixmapFromMimeSource 202 | 203 | 204 | Gui::PrefComboBox 205 | QComboBox 206 |
Gui/PrefWidgets.h
207 |
208 | 209 | Gui::PrefCheckBox 210 | QCheckBox 211 |
Gui/PrefWidgets.h
212 |
213 |
214 | 215 | 216 |
217 | -------------------------------------------------------------------------------- /Resources/panels/SolidCornerReliefPanel.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | SMSolidCornerReliefTaskPanel 4 | 5 | 6 | 7 | 0 8 | 0 9 | 259 10 | 369 11 | 12 | 13 | 14 | Corner relief on solid parameters 15 | 16 | 17 | 18 | 19 | 20 | 0 21 | 22 | 23 | 24 | 25 | Select 26 | 27 | 28 | true 29 | 30 | 31 | false 32 | 33 | 34 | 35 | 36 | 37 | 38 | Clear selection 39 | 40 | 41 | 42 | 43 | 44 | 45 | Qt::Horizontal 46 | 47 | 48 | 49 | 40 50 | 20 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 0 62 | 0 63 | 64 | 65 | 66 | true 67 | 68 | 69 | 70 | Name 71 | 72 | 73 | 74 | 75 | SubElement 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | Extend 84 | 85 | 86 | 87 | 88 | 89 | Qt::Vertical 90 | 91 | 92 | 93 | 20 94 | 40 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | Size 103 | 104 | 105 | 106 | 107 | 108 | 109 | mm 110 | 111 | 112 | 0.000000000000000 113 | 114 | 115 | 116 | 117 | 118 | 119 | false 120 | 121 | 122 | Refine 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | Gui::QuantitySpinBox 134 | QWidget 135 |
Gui/QuantitySpinBox.h
136 |
137 |
138 | 139 | tree 140 | CornerSize 141 | 142 | 143 | 144 |
145 | -------------------------------------------------------------------------------- /Resources/panels/UnfoldSketchOptions.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | SMUnfoldSketchTaskPanel 4 | 5 | 6 | 7 | 0 8 | 0 9 | 278 10 | 180 11 | 12 | 13 | 14 | Projection Sketch 15 | 16 | 17 | 18 | 19 | 20 | Generate projection sketch 21 | 22 | 23 | 24 | 25 | 26 | 27 | QLayout::SetMinimumSize 28 | 29 | 30 | 0 31 | 32 | 33 | 34 | 35 | Color 36 | 37 | 38 | 39 | 40 | 41 | 42 | Qt::Horizontal 43 | 44 | 45 | 46 | 40 47 | 20 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | false 56 | 57 | 58 | 59 | 66 60 | 203 61 | 105 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | false 72 | 73 | 74 | Separate projection layers 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | Bend lines color 84 | 85 | 86 | 87 | 88 | 89 | 90 | Qt::Horizontal 91 | 92 | 93 | 94 | 40 95 | 20 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | false 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | QLayout::SetDefaultConstraint 113 | 114 | 115 | 116 | 117 | Internal lines color 118 | 119 | 120 | 121 | 122 | 123 | 124 | Qt::Horizontal 125 | 126 | 127 | 128 | 40 129 | 20 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | false 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | Qt::Vertical 147 | 148 | 149 | 150 | 20 151 | 40 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | Gui::ColorButton 161 | QPushButton 162 |
Gui/Widgets.h
163 |
164 |
165 | 166 | 167 | 168 | 169 | 170 |
171 | -------------------------------------------------------------------------------- /Resources/panels/WrappedCutoutPanel.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | SMWrappedCutoutTaskPanel 4 | 5 | 6 | 7 | 0 8 | 0 9 | 330 10 | 676 11 | 12 | 13 | 14 | 15 | 0 16 | 0 17 | 18 | 19 | 20 | Wrapped Cutout properties 21 | 22 | 23 | 24 | 25 | 26 | 27 | 0 28 | 0 29 | 30 | 31 | 32 | Base Features 33 | 34 | 35 | 36 | 37 | 38 | 39 | 0 40 | 0 41 | 42 | 43 | 44 | 45 | 0 46 | 47 | 48 | 4 49 | 50 | 51 | 0 52 | 53 | 54 | 4 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | Sketch 63 | 64 | 65 | 66 | 67 | 68 | 69 | Face 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 0 87 | 0 88 | 89 | 90 | 91 | Cutout Parameters 92 | 93 | 94 | 95 | 96 | 97 | QLayout::SetDefaultConstraint 98 | 99 | 100 | 101 | 102 | K-Factor 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 0 111 | 0 112 | 113 | 114 | 115 | 1.000000000000000 116 | 117 | 118 | 0.010000000000000 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | Qt::Vertical 131 | 132 | 133 | 134 | 20 135 | 40 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | Gui::DoubleSpinBox 145 | QWidget 146 |
gui::doublespinbox.h
147 |
148 |
149 | 150 | 151 | 152 | 153 | 154 |
155 | -------------------------------------------------------------------------------- /Resources/sheetmetal_terms.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaise/FreeCAD_SheetMetal/21ee403ab369cc2d08de0dcc989cb1bfd201773d/Resources/sheetmetal_terms.png -------------------------------------------------------------------------------- /Resources/smvideo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaise/FreeCAD_SheetMetal/21ee403ab369cc2d08de0dcc989cb1bfd201773d/Resources/smvideo.jpg -------------------------------------------------------------------------------- /Resources/translations/README.md: -------------------------------------------------------------------------------- 1 | # About translating Sheet Metal Workbench 2 | 3 | ## Translators 4 | 5 | Translations for this workbench is done by visiting the **FreeCAD-addons** 6 | project on CrowdIn platform at webpage, 7 | then find your language, look for the **Sheet Metal** project and do the translation. 8 | 9 | > [!IMPORTANT] 10 | > PR system of translations is no longer accepted. Please use CrowdIn above. 11 | 12 | ## Maintainers 13 | 14 | > [!NOTE] 15 | > All commands **must** be run in `./Resorces/translations/` directory. 16 | 17 | > [!WARNING] 18 | > If you want to update/release the files you need to have installed 19 | > `lupdate` and `lrelease` from Qt6 version. Using the versions from 20 | > Qt5 is not advised because they're buggy. 21 | 22 | ## Updating translations template file 23 | 24 | To update the template file from source files you should use this command: 25 | 26 | ```shell 27 | ./update_translation.sh -U 28 | ``` 29 | 30 | Once done you can commit the changes and upload the new file to CrowdIn platform 31 | at webpage and find the **Sheet Metal** project. 32 | 33 | ## Creating file for missing locale 34 | 35 | This step is not really needed anymore because you should use the CrowdIn platform 36 | to do the translation, take the information here as a reference. 37 | 38 | ### Using script 39 | 40 | To create a file for a new language with all **Sheet Metal** translatable strings execute 41 | the script with `-u` flag plus your locale: 42 | 43 | ```shell 44 | ./update_translation.sh -u de 45 | ``` 46 | 47 | ### Renaming file 48 | 49 | Also you can rename new `Sheet Metal.ts` file by appending the locale code, 50 | for example, `Sheet Metal_de.ts` for German and change 51 | 52 | ```xml 53 | 54 | ``` 55 | 56 | to 57 | 58 | ```xml 59 | 60 | ``` 61 | 62 | As of 2024/10/19 the supported locales on FreeCAD 63 | (according to `FreeCADGui.supportedLocales()`) are 44: 64 | 65 | ```python 66 | {'English': 'en', 'Afrikaans': 'af', 'Arabic': 'ar', 'Basque': 'eu', 67 | 'Belarusian': 'be', 'Bulgarian': 'bg', 'Catalan': 'ca', 68 | 'Chinese Simplified': 'zh-CN', 'Chinese Traditional': 'zh-TW', 'Croatian': 'hr', 69 | 'Czech': 'cs', 'Danish': 'da', 'Dutch': 'nl', 'Filipino': 'fil', 'Finnish': 'fi', 70 | 'French': 'fr', 'Galician': 'gl', 'Georgian': 'ka', 'German': 'de', 'Greek': 'el', 71 | 'Hungarian': 'hu', 'Indonesian': 'id', 'Italian': 'it', 'Japanese': 'ja', 72 | 'Kabyle': 'kab', 'Korean': 'ko', 'Lithuanian': 'lt', 'Norwegian': 'no', 73 | 'Polish': 'pl', 'Portuguese': 'pt-PT', 'Portuguese, Brazilian': 'pt-BR', 74 | 'Romanian': 'ro', 'Russian': 'ru', 'Serbian': 'sr', 'Serbian, Latin': 'sr-CS', 75 | 'Slovak': 'sk', 'Slovenian': 'sl', 'Spanish': 'es-ES', 'Spanish, Argentina': 'es-AR', 76 | 'Swedish': 'sv-SE', 'Turkish': 'tr', 'Ukrainian': 'uk', 'Valencian': 'val-ES', 77 | 'Vietnamese': 'vi'} 78 | ``` 79 | 80 | Alternatively, you can edit your language file on `Qt Linguist` from 81 | `qt5-tools`/`qt6-tools` package (preferred) or in a text editor like `xed`, `mousepad`, 82 | `gedit`, `nano`, `vim`/`nvim`, `geany` etc. (you'll suffer) and translate it. 83 | After translating the file locally upload your changes to CrowdIn platform 84 | in order for it to be synced. 85 | 86 | ## Compiling translations 87 | 88 | To convert all `.ts` files to `.qm` files (merge) you can use this command: 89 | 90 | ```shell 91 | ./update_translation.sh -R 92 | ``` 93 | 94 | If you are a translator that wants to update only their language file 95 | to test it on **FreeCAD** you can use this command: 96 | 97 | ```shell 98 | ./update_translation.sh -r de 99 | ``` 100 | 101 | This will update the `.qm` file for your language (German in this case). 102 | 103 | ## Updating translations 104 | 105 | Some notes for maintainers to consider: 106 | 107 | - First update the `SheetMetal.ts` file then upload it to CrowdIn 108 | - Remember to pull the translations from CrowdIn periodically 109 | - Make sure you're using the correct `lupdate` version 110 | - Include both `.ts` and `.qm` files. 111 | - Make sure changes are working before making a PR 112 | 113 | 114 | 115 | ## More information 116 | 117 | You can read more about translating external workbenches here: 118 | 119 | 120 | -------------------------------------------------------------------------------- /Resources/translations/SheetMetal_de.qm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaise/FreeCAD_SheetMetal/21ee403ab369cc2d08de0dcc989cb1bfd201773d/Resources/translations/SheetMetal_de.qm -------------------------------------------------------------------------------- /Resources/translations/SheetMetal_el.qm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaise/FreeCAD_SheetMetal/21ee403ab369cc2d08de0dcc989cb1bfd201773d/Resources/translations/SheetMetal_el.qm -------------------------------------------------------------------------------- /Resources/translations/SheetMetal_es-ar.qm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaise/FreeCAD_SheetMetal/21ee403ab369cc2d08de0dcc989cb1bfd201773d/Resources/translations/SheetMetal_es-ar.qm -------------------------------------------------------------------------------- /Resources/translations/SheetMetal_es-es.qm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaise/FreeCAD_SheetMetal/21ee403ab369cc2d08de0dcc989cb1bfd201773d/Resources/translations/SheetMetal_es-es.qm -------------------------------------------------------------------------------- /Resources/translations/SheetMetal_fr.qm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaise/FreeCAD_SheetMetal/21ee403ab369cc2d08de0dcc989cb1bfd201773d/Resources/translations/SheetMetal_fr.qm -------------------------------------------------------------------------------- /Resources/translations/SheetMetal_it.qm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaise/FreeCAD_SheetMetal/21ee403ab369cc2d08de0dcc989cb1bfd201773d/Resources/translations/SheetMetal_it.qm -------------------------------------------------------------------------------- /Resources/translations/SheetMetal_pl.qm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaise/FreeCAD_SheetMetal/21ee403ab369cc2d08de0dcc989cb1bfd201773d/Resources/translations/SheetMetal_pl.qm -------------------------------------------------------------------------------- /Resources/translations/SheetMetal_pt-br.qm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaise/FreeCAD_SheetMetal/21ee403ab369cc2d08de0dcc989cb1bfd201773d/Resources/translations/SheetMetal_pt-br.qm -------------------------------------------------------------------------------- /Resources/translations/SheetMetal_pt-pt.qm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaise/FreeCAD_SheetMetal/21ee403ab369cc2d08de0dcc989cb1bfd201773d/Resources/translations/SheetMetal_pt-pt.qm -------------------------------------------------------------------------------- /Resources/translations/SheetMetal_sr-cs.qm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaise/FreeCAD_SheetMetal/21ee403ab369cc2d08de0dcc989cb1bfd201773d/Resources/translations/SheetMetal_sr-cs.qm -------------------------------------------------------------------------------- /Resources/translations/SheetMetal_sr.qm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaise/FreeCAD_SheetMetal/21ee403ab369cc2d08de0dcc989cb1bfd201773d/Resources/translations/SheetMetal_sr.qm -------------------------------------------------------------------------------- /Resources/translations/SheetMetal_zh-tw.qm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaise/FreeCAD_SheetMetal/21ee403ab369cc2d08de0dcc989cb1bfd201773d/Resources/translations/SheetMetal_zh-tw.qm -------------------------------------------------------------------------------- /Resources/translations/update_translation.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # -------------------------------------------------------------------------------------------------- 4 | # 5 | # Create, update and release translation files. 6 | # 7 | # Supported locales on FreeCAD <2024-10-09, FreeCADGui.supportedLocales(), total=44>: 8 | # {'English': 'en', 'Afrikaans': 'af', 'Arabic': 'ar', 'Basque': 'eu', 'Belarusian': 'be', 9 | # 'Bulgarian': 'bg', 'Catalan': 'ca', 'Chinese Simplified': 'zh-CN', 10 | # 'Chinese Traditional': 'zh-TW', 'Croatian': 'hr', 'Czech': 'cs', 'Danish': 'da', 11 | # 'Dutch': 'nl', 'Filipino': 'fil', 'Finnish': 'fi', 'French': 'fr', 'Galician': 'gl', 12 | # 'Georgian': 'ka', 'German': 'de', 'Greek': 'el', 'Hungarian': 'hu', 'Indonesian': 'id', 13 | # 'Italian': 'it', 'Japanese': 'ja', 'Kabyle': 'kab', 'Korean': 'ko', 'Lithuanian': 'lt', 14 | # 'Norwegian': 'no', 'Polish': 'pl', 'Portuguese': 'pt-PT', 'Portuguese, Brazilian': 'pt-BR', 15 | # 'Romanian': 'ro', 'Russian': 'ru', 'Serbian': 'sr', 'Serbian, Latin': 'sr-CS', 'Slovak': 'sk', 16 | # 'Slovenian': 'sl', 'Spanish': 'es-ES', 'Spanish, Argentina': 'es-AR', 'Swedish': 'sv-SE', 17 | # 'Turkish': 'tr', 'Ukrainian': 'uk', 'Valencian': 'val-ES', 'Vietnamese': 'vi'} 18 | # 19 | # NOTE: PREPARATION 20 | # - Install Qt tools 21 | # Debian-based (e.g., Ubuntu): $ sudo apt-get install qttools5-dev-tools pyqt6-dev-tools 22 | # Fedora-based: $ sudo dnf install qt6-linguist qt6-devel 23 | # Arch-based: $ sudo pacman -S qt6-tools python-pyqt6 24 | # - Make the script executable 25 | # $ chmod +x update_translation.sh 26 | # - The script has to be executed within the `Resources/translations` directory. 27 | # Executing the script with no flags invokes the help. 28 | # $ ./update_translation.sh 29 | # 30 | # NOTE: WORKFLOW TRANSLATOR (LOCAL) 31 | # - Execute the script passing the `-u` flag plus locale code as argument 32 | # Only update the file(s) you're translating! 33 | # $ ./update_translation.sh -u es-ES 34 | # - Do the translation via Qt Linguist and use `File>Release` 35 | # - If releasing with the script execute it passing the `-r` flag 36 | # plus locale code as argument 37 | # $ ./update_translation.sh -r es-ES 38 | # 39 | # NOTE: WORKFLOW MAINTAINER (CROWDIN) 40 | # - Execute the script passing the '-U' flag 41 | # $ ./update_translation.sh -U 42 | # - Upload the updated file to CrowdIn and wait for translators do their thing ;-) 43 | # - Once done, download the translated files, copy them to `Resources/translations` 44 | # and release all the files to update the changes 45 | # $ ./update_translation.sh -R 46 | # 47 | # -------------------------------------------------------------------------------------------------- 48 | 49 | supported_locales=( 50 | "en" "af" "ar" "eu" "be" "bg" "ca" "zh-CN" "zh-TW" "hr" 51 | "cs" "da" "nl" "fil" "fi" "fr" "gl" "ka" "de" "el" 52 | "hu" "id" "it" "ja" "kab" "ko" "lt" "no" "pl" "pt-PT" 53 | "pt-BR" "ro" "ru" "sr" "sr-CS" "sk" "sl" "es-ES" "es-AR" "sv-SE" 54 | "tr" "uk" "val-ES" "vi" 55 | ) 56 | 57 | is_locale_supported() { 58 | local locale="$1" 59 | for supported_locale in "${supported_locales[@]}"; do 60 | [ "$supported_locale" == "$locale" ] && return 0 61 | done 62 | return 1 63 | } 64 | 65 | update_locale() { 66 | local locale="$1" 67 | local u=${locale:+_} # Conditional underscore 68 | FILES="../../*.py ../panels/*.ui" 69 | 70 | # NOTE: Execute the right command depending on: 71 | # - if it's a locale file or the main, agnostic one 72 | [ ! -f "${WB}${u}${locale}.ts" ] && action="Creating" || action="Updating" 73 | echo -e "\033[1;34m\n\t<<< ${action} '${WB}${u}${locale}.ts' file >>>\n\033[m" 74 | if [ "$u" == "" ]; then 75 | eval $LUPDATE "$FILES" -ts "${WB}.ts" # locale-agnostic file 76 | else 77 | eval $LUPDATE "$FILES" -source-language en_US -target-language "${locale//-/_}" \ 78 | -ts "${WB}_${locale,,}.ts" 79 | fi 80 | } 81 | 82 | normalize_crowdin_files() { 83 | # Rename files which locales are different on FreeCAD and delete not supported locales 84 | crowdin_fixes=(af-ZA ar-SA be-BY bg-BG ca-ES cs-CZ da-DK de-DE el-GR eu-ES fi-FI 85 | fil-PH fr-FR gl-ES hr-HR hu-HU it-IT ja-JP ka-GE kab-KAB ko-KR lt-LT nl-NL 86 | no-NO pl-PL ro-RO ru-RU sk-SK sl-SI sr-SP tr-TR uk-UA vi-VN) 87 | 88 | crowdin_deletes=(az-AZ bn-BD br-FR bs-BA en en-GB en-US eo-UY es-CO es-VE et-EE fa-IR he-IL 89 | hi-IN hy-AM id-ID kaa lv-LV mk-MK ms-MY sat-IN si-LK ta-IN te-IN th-TH ur-PK xav yo-NG) 90 | 91 | for pattern in "${crowdin_fixes[@]}"; do 92 | find . -type f -name "*_${pattern}\.*" | while read -r file; do 93 | mv -v "$file" "${file//-*./.}" 94 | done 95 | done 96 | 97 | for pattern in "${crowdin_deletes[@]}"; do 98 | find . -type f -name "*_${pattern}\.*" -delete 99 | done 100 | } 101 | 102 | help() { 103 | echo -e "\nDescription:" 104 | echo -e "\tCreate, update and release translation files." 105 | echo -e "\nUsage:" 106 | echo -e "\t./update_translation.sh [-R] [-U] [-r ] [-u ]" 107 | echo -e "\nFlags:" 108 | echo -e " -R\n\tRelease all translations (qm files)" 109 | echo -e " -U\n\tUpdate all translations (ts files)" 110 | echo -e " -r \n\tRelease the specified locale" 111 | echo -e " -u \n\tUpdate strings for the specified locale" 112 | echo -e " -N\n\tNormalize CrowdIn filenames" 113 | } 114 | 115 | # Main function ------------------------------------------------------------------------------------ 116 | 117 | LUPDATE=/usr/lib/qt6/bin/lupdate # from Qt6 118 | # LUPDATE=lupdate # from Qt5 119 | LRELEASE=/usr/lib/qt6/bin/lrelease # from Qt6 120 | # LRELEASE=lrelease # from Qt5 121 | WB="SheetMetal" 122 | 123 | sed -i '3s/-/_/' ${WB}*.ts # Enforce underscore on locales 124 | sed -i '3s/\"en\"/\"en_US\"/g' ${WB}*.ts # Use en_US 125 | 126 | if [ $# -eq 1 ]; then 127 | if [ "$1" == "-R" ]; then 128 | find . -type f -name '*_*.ts' | while IFS= read -r file; do 129 | # Release all locales 130 | $LRELEASE -nounfinished "$file" 131 | echo 132 | done 133 | elif [ "$1" == "-U" ]; then 134 | for locale in "${supported_locales[@]}"; do 135 | update_locale "$locale" 136 | done 137 | elif [ "$1" == "-u" ]; then 138 | update_locale # Update main file (agnostic) 139 | elif [ "$1" == "-N" ]; then 140 | normalize_crowdin_files 141 | else 142 | help 143 | fi 144 | elif [ $# -eq 2 ]; then 145 | LOCALE="$2" 146 | if is_locale_supported "$LOCALE"; then 147 | if [ "$1" == "-r" ]; then 148 | # Release locale (creation of *.qm file from *.ts file) 149 | $LRELEASE -nounfinished "${WB}_${LOCALE,,}.ts" 150 | elif [ "$1" == "-u" ]; then 151 | # Update main & locale files 152 | update_locale 153 | update_locale "$LOCALE" 154 | fi 155 | else 156 | echo "Verify your language code. Case sensitive." 157 | echo "If it's correct, ask a maintainer to add support for your language on FreeCAD." 158 | echo -e "\nSupported locales, '\033[1;34mFreeCADGui.supportedLocales()\033[m': \033[1;33m" 159 | for locale in $(printf "%s\n" "${supported_locales[@]}" | sort); do 160 | echo -n "$locale " 161 | done 162 | echo 163 | fi 164 | else 165 | help 166 | fi 167 | -------------------------------------------------------------------------------- /SMTests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaise/FreeCAD_SheetMetal/21ee403ab369cc2d08de0dcc989cb1bfd201773d/SMTests/__init__.py -------------------------------------------------------------------------------- /SMTests/testFolder.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # ####################################################################### 3 | # 4 | # Copyright (c) 2023 Ondsel Inc. 5 | # 6 | # This program is free software; you can redistribute it and/or 7 | # modify it under the terms of the GNU Lesser General Public 8 | # License as published by the Free Software Foundation; either 9 | # version 2 of the License, or (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU Lesser General Public 17 | # License along with this program; if not, write to the Free Software 18 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 19 | # MA 02110-1301, USA. 20 | # 21 | # 22 | # ####################################################################### 23 | 24 | 25 | import unittest 26 | import FreeCAD as App 27 | import Part 28 | from SheetMetalBendSolid import ( 29 | get_point_on_cylinder, 30 | wrap_bspline, 31 | wrap_face, 32 | bend_solid, 33 | ) 34 | from FreeCAD import Vector 35 | 36 | 37 | class TestFolder(unittest.TestCase): 38 | @classmethod 39 | def setUpClass(cls): 40 | # Setup code that runs before all tests start. 41 | # This could include creating a FreeCAD document. 42 | cls.doc = App.newDocument() 43 | 44 | def test_get_point_on_cylinder(self): 45 | # test data generated from a known working model. Not otherwise 46 | # validated 47 | 48 | data = [ 49 | { 50 | "zero_vert": Vector(631.5394914952352, 377.0, 3.17), 51 | "point": Vector(631.5394914952352, -123.00000000000028, 3.17), 52 | "radius": 2.585, 53 | "center": Vector(631.5394914952352, 377.0, 4.17), 54 | "axis": Vector(0.0, -1.0, 0.0), 55 | "zero_vert_normal": Vector(-1.0, -0.0, 0.0), 56 | "expected": Vector( 57 | 631.875387629876, -123.00000000000028, 3.2281009678668093 58 | ), 59 | }, 60 | { 61 | "zero_vert": Vector(631.5394914952352, 377.0, 3.17), 62 | "point": Vector(631.5394914952352, -123.00000000000028, 3.17), 63 | "radius": 2.585, 64 | "center": Vector(631.5394914952352, 377.0, 4.17), 65 | "axis": Vector(0.0, -1.0, 0.0), 66 | "zero_vert_normal": Vector(-1.0, -0.0, 0.0), 67 | "expected": Vector( 68 | 632.3567723938988, -123.00000000000034, 3.5937605248860045 69 | ), 70 | }, 71 | { 72 | "zero_vert": Vector(631.5394914952352, 377.0, 3.17), 73 | "point": Vector(631.5394914952352, -123.00000000000028, 3.17), 74 | "radius": 2.585, 75 | "center": Vector(631.5394914952352, 377.0, 4.17), 76 | "axis": Vector(0.0, -1.0, 0.0), 77 | "zero_vert_normal": Vector(-1.0, -0.0, 0.0), 78 | "expected": Vector( 79 | 632.5394914952352, -123.00000000000034, 4.170000000000002 80 | ), 81 | }, 82 | { 83 | "zero_vert": Vector(631.5394914952352, 377.0, 3.17), 84 | "point": Vector(631.5394914952352, -123.0, 3.17), 85 | "radius": 2.585, 86 | "center": Vector(631.5394914952352, 377.0, 4.17), 87 | "axis": Vector(0.0, -1.0, 0.0), 88 | "zero_vert_normal": Vector(-1.0, -0.0, 0.0), 89 | "expected": Vector(631.8753876298758, -123.0, 3.2281009678667942), 90 | }, 91 | { 92 | "zero_vert": Vector(631.5394914952352, 377.0, 3.17), 93 | "point": Vector(631.5394914952352, -123.0, 3.17), 94 | "radius": 2.585, 95 | "center": Vector(631.5394914952352, 377.0, 4.17), 96 | "axis": Vector(0.0, -1.0, 0.0), 97 | "zero_vert_normal": Vector(-1.0, -0.0, 0.0), 98 | "expected": Vector(631.709933520374, -123.0, 3.184632293980257), 99 | }, 100 | { 101 | "zero_vert": Vector(631.5394914952352, 377.0, 3.17), 102 | "point": Vector(631.5394914952352, -123.0, 3.17), 103 | "radius": 2.585, 104 | "center": Vector(631.5394914952352, 377.0, 4.17), 105 | "axis": Vector(0.0, -1.0, 0.0), 106 | "zero_vert_normal": Vector(-1.0, -0.0, 0.0), 107 | "expected": Vector(631.5394914952352, -123.0, 3.17), 108 | }, 109 | { 110 | "zero_vert": Vector(631.5394914952352, 377.0, 3.17), 111 | "point": Vector(631.5394914952352, 377.0, 3.17), 112 | "radius": 2.585, 113 | "center": Vector(631.5394914952352, 377.0, 4.17), 114 | "axis": Vector(0.0, -1.0, 0.0), 115 | "zero_vert_normal": Vector(-1.0, -0.0, 0.0), 116 | "expected": Vector(631.5394914952352, 377.0, 3.17), 117 | }, 118 | ] 119 | 120 | for d in data: 121 | result = get_point_on_cylinder( 122 | d["zero_vert"], 123 | d["point"], 124 | d["radius"], 125 | d["center"], 126 | d["axis"], 127 | d["zero_vert_normal"], 128 | ) 129 | d["expected"] = result 130 | 131 | self.assertEqual(result, d["expected"]) 132 | 133 | @classmethod 134 | def tearDownClass(cls): 135 | # Cleanup code that runs after all tests are complete. 136 | # This could include closing the FreeCAD document. 137 | App.closeDocument(cls.doc.Name) 138 | 139 | 140 | if __name__ == "__main__": 141 | unittest.main() 142 | -------------------------------------------------------------------------------- /SMTests/testKfactor.py: -------------------------------------------------------------------------------- 1 | # ####################################################################### 2 | # 3 | # Copyright (c) 2023 Ondsel Inc. 4 | # 5 | # This program is free software; you can redistribute it and/or 6 | # modify it under the terms of the GNU Lesser General Public 7 | # License as published by the Free Software Foundation; either 8 | # version 2 of the License, or (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Lesser General Public 16 | # License along with this program; if not, write to the Free Software 17 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 18 | # MA 02110-1301, USA. 19 | # 20 | # 21 | # ####################################################################### 22 | 23 | import unittest 24 | import FreeCAD 25 | from SheetMetalKfactor import KFactorLookupTable 26 | 27 | 28 | class TestKFactor(unittest.TestCase): 29 | @classmethod 30 | def setUpClass(cls): 31 | pass 32 | 33 | @classmethod 34 | def tearDownClass(cls): 35 | pass 36 | 37 | def test_00(self): 38 | 39 | doc = FreeCAD.newDocument() 40 | sheet = doc.addObject("Spreadsheet::Sheet", "material_foo") 41 | 42 | sheet.Label = "KFactor" 43 | 44 | sheet.set("A1", "Radius / Thickness") 45 | sheet.set("B1", "K-factor (ANSI)") 46 | sheet.set("A2", "1") 47 | sheet.set("B2", "0.38") 48 | sheet.set("A3", "3") 49 | sheet.set("B3", "0.43") 50 | sheet.set("A4", "99") 51 | sheet.set("B4", "0.5") 52 | sheet.recompute() 53 | 54 | c = KFactorLookupTable(sheet.Label) 55 | self.assertTrue(c.k_factor_lookup[1] == 0.38) 56 | self.assertTrue(c.k_factor_lookup[3] == 0.43) 57 | self.assertTrue(c.k_factor_lookup[99] == 0.5) 58 | self.assertTrue(c.k_factor_standard == "ansi") 59 | 60 | 61 | if __name__ == "__main__": 62 | unittest.main() 63 | -------------------------------------------------------------------------------- /SheetMetalBend.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | ############################################################################## 3 | # 4 | # SheetMetalBend.py 5 | # 6 | # Copyright 2015 Shai Seger 7 | # 8 | # This program is free software; you can redistribute it and/or 9 | # modify it under the terms of the GNU Lesser General Public 10 | # License as published by the Free Software Foundation; either 11 | # version 2 of the License, or (at your option) any later version. 12 | # 13 | # This program is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU Lesser General Public 19 | # License along with this program; if not, write to the Free Software 20 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 21 | # MA 02110-1301, USA. 22 | # 23 | # 24 | ############################################################################## 25 | 26 | import os 27 | import Part 28 | import FreeCAD 29 | import SheetMetalTools 30 | 31 | smEpsilon = SheetMetalTools.smEpsilon 32 | 33 | # IMPORTANT: please remember to change the element map version in case of any 34 | # changes in modeling logic 35 | smElementMapVersion = "sm1." 36 | 37 | # list of properties to be saved as defaults 38 | smAddBendDefaultVars = [("radius", "defaultRadius")] 39 | 40 | 41 | def smGetClosestVert(vert, face): 42 | closestVert = None 43 | closestDist = 99999999 44 | for v in face.Vertexes: 45 | if vert.isSame(v): 46 | continue 47 | d = vert.distToShape(v)[0] 48 | if d < closestDist: 49 | closestDist = d 50 | closestVert = v 51 | return closestVert 52 | 53 | 54 | # we look for a matching inner edge to the selected outer one 55 | # this function finds a single vertex of that edge 56 | def smFindMatchingVert(shape, edge, vertid): 57 | facelist = shape.ancestorsOfType(edge, Part.Face) 58 | edgeVerts = edge.Vertexes 59 | v = edgeVerts[vertid] 60 | vfacelist = shape.ancestorsOfType(v, Part.Face) 61 | 62 | # find the face that is not in facelist 63 | for vface in vfacelist: 64 | if not vface.isSame(facelist[0]) and not vface.isSame(facelist[1]): 65 | break 66 | 67 | return smGetClosestVert(v, vface) 68 | 69 | 70 | def smFindEdgeByVerts(shape, vert1, vert2): 71 | for edge in shape.Edges: 72 | if vert1.isSame(edge.Vertexes[0]) and vert2.isSame(edge.Vertexes[1]): 73 | break 74 | if vert1.isSame(edge.Vertexes[1]) and vert2.isSame(edge.Vertexes[0]): 75 | break 76 | else: 77 | edge = None 78 | return edge 79 | 80 | 81 | def smSolidBend(radius=1.0, selEdgeNames="", MainObject=None): 82 | InnerEdgesToBend = [] 83 | OuterEdgesToBend = [] 84 | for selEdgeName in selEdgeNames: 85 | edge = MainObject.getElement(SheetMetalTools.getElementFromTNP(selEdgeName)) 86 | 87 | # find matching inner edge to selected outer one 88 | v1 = smFindMatchingVert(MainObject, edge, 0) 89 | v2 = smFindMatchingVert(MainObject, edge, 1) 90 | matchingEdge = smFindEdgeByVerts(MainObject, v1, v2) 91 | if matchingEdge is not None: 92 | InnerEdgesToBend.append(matchingEdge) 93 | OuterEdgesToBend.append(edge) 94 | 95 | if len(InnerEdgesToBend) > 0: 96 | # find thickness of sheet by distance from v1 to one of the edges coming out of edge[0] 97 | # we assume all corners have same thickness 98 | for dedge in MainObject.ancestorsOfType(edge.Vertexes[0], Part.Edge): 99 | if not dedge.isSame(edge): 100 | break 101 | 102 | thickness = v1.distToShape(dedge)[0] 103 | 104 | resultSolid = MainObject.makeFillet(radius, InnerEdgesToBend) 105 | resultSolid = resultSolid.makeFillet(radius + thickness, OuterEdgesToBend) 106 | 107 | return resultSolid 108 | 109 | 110 | class SMSolidBend: 111 | def __init__(self, obj, selobj, sel_elements): 112 | """ "Add Bend to Solid" """ 113 | 114 | self.addVerifyProperties(obj) 115 | _tip_ = FreeCAD.Qt.translate("App::Property", "Base object") 116 | obj.addProperty( 117 | "App::PropertyLinkSub", "baseObject", "Parameters", _tip_ 118 | ).baseObject = (selobj, sel_elements) 119 | obj.Proxy = self 120 | SheetMetalTools.taskRestoreDefaults(obj, smAddBendDefaultVars) 121 | 122 | def addVerifyProperties(self, obj): 123 | SheetMetalTools.smAddLengthProperty( 124 | obj, "radius", FreeCAD.Qt.translate("App::Property", "Bend Radius"), 1.0 125 | ) 126 | # SheetMetalTools.smAddBoolProperty( 127 | # obj, "Refine", FreeCAD.Qt.translate("App::Property", "Use Refine"), False 128 | # ) 129 | 130 | def getElementMapVersion(self, _fp, ver, _prop, restored): 131 | if not restored: 132 | return smElementMapVersion + ver 133 | 134 | def execute(self, fp): 135 | """ "Print a short message when doing a recomputation, this method is mandatory" """ 136 | self.addVerifyProperties(fp) 137 | Main_Object = fp.baseObject[0].Shape.copy() 138 | s = smSolidBend( 139 | radius=fp.radius.Value, 140 | selEdgeNames=fp.baseObject[1], 141 | MainObject=Main_Object, 142 | ) 143 | fp.Shape = s 144 | 145 | 146 | ############################################################################################## 147 | # Gui code 148 | ############################################################################################## 149 | 150 | if SheetMetalTools.isGuiLoaded(): 151 | from FreeCAD import Gui 152 | 153 | icons_path = SheetMetalTools.icons_path 154 | 155 | class SMBendViewProviderTree(SheetMetalTools.SMViewProvider): 156 | ''' Part WB style ViewProvider ''' 157 | def getIcon(self): 158 | return os.path.join(icons_path, 'SheetMetal_AddBend.svg') 159 | 160 | def getTaskPanel(self, obj): 161 | return SMBendTaskPanel(obj) 162 | 163 | class SMBendViewProviderFlat(SMBendViewProviderTree): 164 | ''' Part Design WB style ViewProvider - backward compatibility only''' 165 | 166 | class SMBendTaskPanel: 167 | """A TaskPanel for the Sheetmetal""" 168 | 169 | def __init__(self, obj): 170 | self.obj = obj 171 | self.form = SheetMetalTools.taskLoadUI("BendCornerPanel.ui") 172 | obj.Proxy.addVerifyProperties(obj) # Make sure all properties are added 173 | self.selParams = SheetMetalTools.taskConnectSelection( 174 | self.form.AddRemove, self.form.tree, self.obj, ["Edge"], self.form.pushClearSel 175 | ) 176 | SheetMetalTools.taskConnectSpin(obj, self.form.Radius, "radius") 177 | # SheetMetalTools.taskConnectCheck(obj, self.form.RefineCheckbox, "Refine") 178 | 179 | def isAllowedAlterSelection(self): 180 | return True 181 | 182 | def isAllowedAlterView(self): 183 | return True 184 | 185 | def accept(self): 186 | SheetMetalTools.taskAccept(self) 187 | SheetMetalTools.taskSaveDefaults(self.obj, smAddBendDefaultVars) 188 | return True 189 | 190 | def reject(self): 191 | SheetMetalTools.taskReject(self) 192 | 193 | #def retranslateUi(self, SMBendTaskPanel): 194 | 195 | class AddBendCommandClass: 196 | """Add Solid Bend command""" 197 | 198 | def GetResources(self): 199 | return { 200 | "Pixmap": os.path.join( 201 | icons_path, "SheetMetal_AddBend.svg" 202 | ), # the name of a svg file available in the resources 203 | "MenuText": FreeCAD.Qt.translate("SheetMetal", "Make Bend"), 204 | "Accel": "S, B", 205 | "ToolTip": FreeCAD.Qt.translate( 206 | "SheetMetal", 207 | "Create Bend where two walls come together on solids\n" 208 | "1. Select edge(s) to create bend on corner edge(s).\n" 209 | "2. Use Property editor to modify parameters", 210 | ), 211 | } 212 | 213 | def Activated(self): 214 | sel = Gui.Selection.getSelectionEx()[0] 215 | selobj = sel.Object 216 | newObj, activeBody = SheetMetalTools.smCreateNewObject(selobj, "SolidBend") 217 | if newObj is None: 218 | return 219 | SMSolidBend(newObj, selobj, sel.SubElementNames) 220 | SMBendViewProviderFlat(newObj.ViewObject) 221 | SheetMetalTools.smAddNewObject( 222 | selobj, newObj, activeBody, SMBendTaskPanel) 223 | return 224 | 225 | def IsActive(self): 226 | if (len(Gui.Selection.getSelection()) < 1 227 | or len(Gui.Selection.getSelectionEx()[0].SubElementNames) < 1): 228 | return False 229 | for selFace in Gui.Selection.getSelectionEx()[0].SubObjects: 230 | if not isinstance(selFace, Part.Edge): 231 | return False 232 | return True 233 | 234 | Gui.addCommand("SheetMetal_AddBend", AddBendCommandClass()) 235 | -------------------------------------------------------------------------------- /SheetMetalJunction.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | ################################################################################### 3 | # 4 | # SheetMetalJunction.py 5 | # 6 | # Copyright 2015 Shai Seger 7 | # 8 | # This program is free software; you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation; either version 2 of the License, or 11 | # (at your option) any later version. 12 | # 13 | # This program is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU General Public License 19 | # along with this program; if not, write to the Free Software 20 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 21 | # MA 02110-1301, USA. 22 | # 23 | # 24 | ################################################################################### 25 | 26 | import os 27 | import FreeCAD 28 | import Part 29 | import SheetMetalTools 30 | 31 | # IMPORTANT: please remember to change the element map version in case of any 32 | # changes in modeling logic 33 | smElementMapVersion = 'sm1.' 34 | 35 | # list of properties to be saved as defaults 36 | smJunctionDefaultVars = [("gap", "defaultJunctionGap")] 37 | 38 | 39 | def smJunction(gap=2.0, selEdgeNames='', MainObject=None): 40 | import BOPTools.SplitFeatures 41 | import BOPTools.JoinFeatures 42 | 43 | resultSolid = MainObject 44 | for selEdgeName in selEdgeNames: 45 | edge = MainObject.getElement( 46 | SheetMetalTools.getElementFromTNP(selEdgeName)) 47 | 48 | facelist = MainObject.ancestorsOfType(edge, Part.Face) 49 | # for face in facelist : 50 | # Part.show(face,'face') 51 | 52 | joinface = facelist[0].fuse(facelist[1]) 53 | # Part.show(joinface,'joinface') 54 | filletedface = joinface.makeFillet(gap, joinface.Edges) 55 | # Part.show(filletedface,'filletedface') 56 | 57 | cutface1 = facelist[0].cut(filletedface) 58 | # Part.show(cutface1,'cutface1') 59 | offsetsolid1 = cutface1.makeOffsetShape(-gap, 0.0, fill=True) 60 | # Part.show(offsetsolid1,'offsetsolid1') 61 | 62 | cutface2 = facelist[1].cut(filletedface) 63 | # Part.show(cutface2,'cutface2') 64 | offsetsolid2 = cutface2.makeOffsetShape(-gap, 0.0, fill=True) 65 | # Part.show(offsetsolid2,'offsetsolid2') 66 | cutsolid = offsetsolid1.fuse(offsetsolid2) 67 | # Part.show(cutsolid,'cutsolid') 68 | resultSolid = resultSolid.cut(cutsolid) 69 | # Part.show(resultsolid,'resultsolid') 70 | 71 | return resultSolid 72 | 73 | 74 | class SMJunction: 75 | def __init__(self, obj, selobj, sel_items): 76 | '''"Add Gap to Solid" ''' 77 | 78 | _tip_ = FreeCAD.Qt.translate("App::Property", "Junction Gap") 79 | obj.addProperty("App::PropertyLength", "gap", 80 | "Parameters", _tip_).gap = 2.0 81 | _tip_ = FreeCAD.Qt.translate("App::Property", "Base Object") 82 | obj.addProperty("App::PropertyLinkSub", "baseObject", "Parameters", 83 | _tip_).baseObject = (selobj, sel_items) 84 | obj.Proxy = self 85 | SheetMetalTools.taskRestoreDefaults(obj, smJunctionDefaultVars) 86 | 87 | def addVerifyProperties(self, obj): 88 | pass 89 | 90 | def getElementMapVersion(self, _fp, ver, _prop, restored): 91 | if not restored: 92 | return smElementMapVersion + ver 93 | 94 | def execute(self, fp): 95 | '''"Print a short message when doing a recomputation, this method is mandatory" ''' 96 | # pass selected object shape 97 | Main_Object = fp.baseObject[0].Shape.copy() 98 | s = smJunction(gap=fp.gap.Value, 99 | selEdgeNames=fp.baseObject[1], MainObject=Main_Object) 100 | fp.Shape = s 101 | SheetMetalTools.smHideObjects(fp.baseObject[0]) 102 | 103 | 104 | ########################################################################################################## 105 | # Gui code 106 | ########################################################################################################## 107 | 108 | if SheetMetalTools.isGuiLoaded(): 109 | from FreeCAD import Gui 110 | from PySide import QtCore, QtGui 111 | 112 | icons_path = SheetMetalTools.icons_path 113 | 114 | # add translations path 115 | Gui.addLanguagePath(SheetMetalTools.language_path) 116 | Gui.updateLocale() 117 | 118 | class SMJViewProviderTree(SheetMetalTools.SMViewProvider): 119 | ''' Part WB style ViewProvider ''' 120 | def getIcon(self): 121 | return os.path.join(icons_path, 'SheetMetal_AddJunction.svg') 122 | 123 | def getTaskPanel(self, obj): 124 | return SMJunctionTaskPanel(obj) 125 | 126 | 127 | class SMJViewProviderFlat(SMJViewProviderTree): 128 | ''' Part Design WB style ViewProvider - backward compatibility only''' 129 | 130 | 131 | class SMJunctionTaskPanel: 132 | '''A TaskPanel for the Sheetmetal addJunction command''' 133 | 134 | def __init__(self, obj): 135 | self.obj = obj 136 | self.form = SheetMetalTools.taskLoadUI("AddJunctionPanel.ui") 137 | obj.Proxy.addVerifyProperties(obj) # Make sure all properties are added 138 | self.selParams = SheetMetalTools.taskConnectSelection( 139 | self.form.AddRemove, self.form.tree, self.obj, ["Edge"], self.form.pushClearSel 140 | ) 141 | SheetMetalTools.taskConnectSpin(obj, self.form.JunctionWidth, "gap") 142 | # SheetMetalTools.taskConnectCheck(obj, self.form.RefineCheckbox, "Refine") 143 | 144 | def isAllowedAlterSelection(self): 145 | return True 146 | 147 | def isAllowedAlterView(self): 148 | return True 149 | 150 | def accept(self): 151 | SheetMetalTools.taskAccept(self) 152 | SheetMetalTools.taskSaveDefaults(self.obj, smJunctionDefaultVars) 153 | return True 154 | 155 | def reject(self): 156 | SheetMetalTools.taskReject(self) 157 | 158 | def retranslateUi(self, TaskPanel): 159 | self.addButton.setText( 160 | QtGui.QApplication.translate("draft", "Update", None)) 161 | 162 | class AddJunctionCommandClass(): 163 | """Add Junction command""" 164 | 165 | def GetResources(self): 166 | return {'Pixmap': os.path.join(icons_path, 'SheetMetal_AddJunction.svg'), # the name of a svg file available in the resources 167 | 'MenuText': FreeCAD.Qt.translate('SheetMetal', 'Make Junction'), 168 | 'Accel': "S, J", 169 | 'ToolTip': FreeCAD.Qt.translate('SheetMetal', 'Create a rip where two walls come together on solids.\n' 170 | '1. Select edge(s) to create rip on corner edge(s).\n' 171 | '2. Use Property editor to modify parameters')} 172 | 173 | def Activated(self): 174 | sel = Gui.Selection.getSelectionEx()[0] 175 | selobj = sel.Object 176 | newObj, activeBody = SheetMetalTools.smCreateNewObject(selobj, "Junction") 177 | if newObj is None: 178 | return 179 | SMJunction(newObj, selobj, sel.SubElementNames) 180 | SMJViewProviderTree(newObj.ViewObject) 181 | SheetMetalTools.smAddNewObject(selobj, newObj, activeBody, SMJunctionTaskPanel) 182 | return 183 | 184 | def IsActive(self): 185 | sel = Gui.Selection.getSelectionEx()[0] 186 | if len(Gui.Selection.getSelection()) < 1 or len(sel.SubElementNames) < 1: 187 | return False 188 | # selobj = Gui.Selection.getSelection()[0] 189 | for selEdge in sel.SubObjects: 190 | if not isinstance(selEdge, Part.Edge): 191 | return False 192 | return True 193 | 194 | Gui.addCommand("SheetMetal_AddJunction", AddJunctionCommandClass()) 195 | -------------------------------------------------------------------------------- /SheetMetalKfactor.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | ################################################################################### 3 | # 4 | # SheetMetalKfactor.py 5 | # 6 | # Copyright 2014, 2018 Ulrich Brammer 7 | # Copyright 2023 Ondsel Inc. 8 | # 9 | # This program is free software; you can redistribute it and/or 10 | # modify it under the terms of the GNU Lesser General Public 11 | # License as published by the Free Software Foundation; either 12 | # version 2 of the License, or (at your option) any later version. 13 | # 14 | # This 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 General Public License for more details. 18 | # 19 | # You should have received a copy of the GNU Lesser General Public 20 | # License along with this program; if not, write to the Free Software 21 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 22 | # MA 02110-1301, USA. 23 | # 24 | # 25 | ################################################################################### 26 | 27 | import re 28 | import FreeCAD 29 | 30 | 31 | def findObjectsByTypeRecursive(doc, tp): 32 | return _find_objects(doc.Objects, lambda obj: obj and obj.isDerivedFrom(tp)) 33 | 34 | 35 | def _find_objects(objs, _filter): 36 | res = [] 37 | queue = list(objs) 38 | visited = set(objs) 39 | while queue: 40 | obj = queue.pop(0) 41 | r = _filter(obj) 42 | if r: 43 | res.append(obj) 44 | if r > 1: 45 | break 46 | elif r < 0: 47 | break 48 | else: 49 | linked = obj.getLinkedObject() 50 | if linked not in visited: 51 | visited.add(linked) 52 | queue.append(linked) 53 | try: 54 | names = obj.getSubObjects() 55 | except Exception: 56 | names = [] 57 | for name in names: 58 | sobj = obj.getSubObject(name, retType=1) 59 | if sobj not in visited: 60 | visited.add(sobj) 61 | queue.append(sobj) 62 | return res 63 | 64 | 65 | def getSpreadSheetNames(): 66 | material_sheet_regex_str = r"material_([a-zA-Z0-9_\-\[\]\.]+)" 67 | material_sheet_regex = re.compile(material_sheet_regex_str) 68 | 69 | spreadsheets = findObjectsByTypeRecursive( 70 | FreeCAD.ActiveDocument, "Spreadsheet::Sheet" 71 | ) 72 | candidateSpreadSheets = [ 73 | o for o in spreadsheets if material_sheet_regex.match(o.Label) 74 | ] 75 | 76 | availableMdsObjects = [] 77 | for candidate in candidateSpreadSheets: 78 | try: 79 | KFactorLookupTable(candidate.Label) 80 | availableMdsObjects.append(candidate) 81 | except ValueError as e: 82 | FreeCAD.Console.PrintWarning( 83 | f"Spreadsheet with name {candidate.Label} is not a valid material definition table.\n" 84 | ) 85 | FreeCAD.Console.PrintWarning(f"Error: {e}\n") 86 | 87 | return availableMdsObjects 88 | 89 | 90 | class KFactorLookupTable: 91 | cell_regex = re.compile(r"^([A-Z]+)([0-9]+)$") 92 | 93 | def __init__(self, material_sheet): 94 | lookup_sheet = FreeCAD.ActiveDocument.getObjectsByLabel(material_sheet) 95 | if len(lookup_sheet) >= 1: 96 | lookup_sheet = lookup_sheet[0] 97 | else: 98 | raise ValueError( 99 | "No spreadsheet found containing material definition: %s" 100 | % material_sheet 101 | ) 102 | 103 | key_cell = self.find_cell_by_label(lookup_sheet, "Radius / Thickness") 104 | value_cell, k_factor_standard = self.find_k_factor_cell(lookup_sheet) 105 | 106 | if key_cell is None: 107 | raise ValueError("No cell found with label: 'Radius / Thickness'") 108 | if value_cell is None: 109 | raise ValueError("No cell found with label: 'K-factor (ANSI/DIN)'") 110 | if k_factor_standard is None: 111 | raise ValueError("No 'Options' column or 'K-factor (????)' cell found.") 112 | 113 | key_column_name, key_column_row = self.get_cell_tuple(key_cell) 114 | value_column_name = self.get_cell_tuple(value_cell)[0] 115 | 116 | k_factor_lookup = self.build_k_factor_lookup( 117 | lookup_sheet, key_column_name, key_column_row, value_column_name 118 | ) 119 | 120 | options_cell = self.find_cell_by_label(lookup_sheet, "Options") 121 | k_factor_standard = self.get_k_factor_standard( 122 | lookup_sheet, options_cell, k_factor_standard 123 | ) 124 | 125 | if k_factor_standard not in ["ansi", "din"]: 126 | raise ValueError("Invalid K-factor standard: %s" % k_factor_standard) 127 | 128 | self.k_factor_lookup = k_factor_lookup 129 | self.k_factor_standard = k_factor_standard 130 | 131 | def get_cells(self, sheet): 132 | return sorted(filter(self.cell_regex.search, sheet.PropertiesList)) 133 | 134 | def get_cell_tuple(self, cell_name): 135 | m = self.cell_regex.match(cell_name) 136 | col_name = m.group(1) 137 | row_num = int(m.group(2)) 138 | return (col_name, row_num) 139 | 140 | def find_cell_by_label(self, sheet, label): 141 | for cell in self.get_cells(sheet): 142 | content = sheet.get(cell) 143 | if str(content).strip() == label: 144 | return cell 145 | return None 146 | 147 | def find_k_factor_cell(self, sheet): 148 | k_factor_standard = None 149 | value_cell = None 150 | for cell in self.get_cells(sheet): 151 | content = sheet.get(cell) 152 | if content == "Radius / Thickness": 153 | key_cell = cell 154 | try: 155 | m = re.search(r"(K-[fF]actor)\s?\(?([a-zA-Z]*)\)?", content) 156 | if m: 157 | value_cell = cell 158 | k_factor_standard = m.group(2).lower() or None 159 | except: 160 | pass 161 | return value_cell, k_factor_standard 162 | 163 | def build_k_factor_lookup( 164 | self, sheet, key_column_name, key_column_row, value_column_name 165 | ): 166 | k_factor_lookup = {} 167 | for i in range(key_column_row + 1, 1000): 168 | try: 169 | key = float(sheet.get(key_column_name + str(i))) 170 | value = float(sheet.get(value_column_name + str(i))) 171 | k_factor_lookup[key] = value 172 | except (ValueError, TypeError): 173 | break 174 | return k_factor_lookup 175 | 176 | def get_k_factor_standard(self, sheet, options_cell, k_factor_standard): 177 | if options_cell is not None: 178 | opt_col, opt_row = self.get_cell_tuple(options_cell) 179 | i = 1 180 | while True: 181 | opt_key_cell = "%s%i" % (opt_col, opt_row + i) 182 | next_col = chr(ord(opt_col) + 1) 183 | opt_value_cell = "%s%i" % (next_col, opt_row + i) 184 | i += 1 185 | try: 186 | option = sheet.get(opt_key_cell) 187 | value = sheet.get(opt_value_cell) 188 | if option == "K-factor standard": 189 | if k_factor_standard is not None: 190 | raise ValueError("Multiple K-factor definitions found") 191 | k_factor_standard = value.lower() 192 | except: 193 | break 194 | if k_factor_standard is None: 195 | raise ValueError("'K-factor standard' option is required (ANSI or DIN)") 196 | return k_factor_standard 197 | -------------------------------------------------------------------------------- /SheetMetalLogger.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | ################################################################################### 3 | # 4 | # SheetMetalLogger.py 5 | # 6 | # Copyright 2014, 2018 Ulrich Brammer 7 | # Copyright 2023 Ondsel Inc. 8 | # 9 | # This program is free software; you can redistribute it and/or 10 | # modify it under the terms of the GNU Lesser General Public 11 | # License as published by the Free Software Foundation; either 12 | # version 2 of the License, or (at your option) any later version. 13 | # 14 | # This 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 General Public License for more details. 18 | # 19 | # You should have received a copy of the GNU Lesser General Public 20 | # License along with this program; if not, write to the Free Software 21 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 22 | # MA 02110-1301, USA. 23 | # 24 | # 25 | ################################################################################### 26 | 27 | import FreeCAD 28 | 29 | class SMLogger: 30 | @classmethod 31 | def error(cls, *args): 32 | message = "" 33 | for x in args: 34 | message += str(x) 35 | FreeCAD.Console.PrintError(message + "\n") 36 | 37 | @classmethod 38 | def log(cls, *args): 39 | message = "" 40 | for x in args: 41 | message += str(x) 42 | FreeCAD.Console.PrintLog(message + "\n") 43 | 44 | @classmethod 45 | def message(cls, *args): 46 | message = "" 47 | for x in args: 48 | message += str(x) 49 | FreeCAD.Console.PrintMessage(message + "\n") 50 | 51 | @classmethod 52 | def warning(cls, *args): 53 | message = "" 54 | for x in args: 55 | message += str(x) 56 | FreeCAD.Console.PrintWarning(message + "\n") 57 | 58 | 59 | 60 | class UnfoldException(Exception): 61 | pass 62 | 63 | class BendException(Exception): 64 | pass 65 | 66 | class TreeException(Exception): 67 | pass 68 | 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /TestSheetMetal.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # ####################################################################### 3 | # 4 | # Copyright (c) 2023 Ondsel Inc. 5 | # 6 | # This program is free software; you can redistribute it and/or 7 | # modify it under the terms of the GNU Lesser General Public 8 | # License as published by the Free Software Foundation; either 9 | # version 2 of the License, or (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU Lesser General Public 17 | # License along with this program; if not, write to the Free Software 18 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 19 | # MA 02110-1301, USA. 20 | # 21 | # 22 | # ####################################################################### 23 | 24 | import TestApp 25 | 26 | from SMTests.testFolder import TestFolder 27 | from SMTests.testKfactor import TestKFactor 28 | -------------------------------------------------------------------------------- /engineering_mode.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | ################################################################################### 3 | # 4 | # engineering_mode.py 5 | # 6 | # Copyright 2019 Cerem Cem Aslan 7 | # 8 | # This program is free software; you can redistribute it and/or 9 | # modify it under the terms of the GNU Lesser General Public 10 | # License as published by the Free Software Foundation; either 11 | # version 2 of the License, or (at your option) any later version. 12 | # 13 | # This program is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU Lesser General Public 19 | # License along with this program; if not, write to the Free Software 20 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 21 | # MA 02110-1301, USA. 22 | # 23 | # 24 | ################################################################################### 25 | 26 | import FreeCAD 27 | 28 | 29 | def engineering_mode_enabled(): 30 | FSParam = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/SheetMetal") 31 | return FSParam.GetInt("EngineeringUXMode", 0) # 0 = disabled, 1 = enabled 32 | -------------------------------------------------------------------------------- /lookup.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | ################################################################################### 3 | # 4 | # lookup.py 5 | # 6 | # Copyright 2019 Cerem Cem Aslan 7 | # 8 | # This program is free software; you can redistribute it and/or 9 | # modify it under the terms of the GNU Lesser General Public 10 | # License as published by the Free Software Foundation; either 11 | # version 2 of the License, or (at your option) any later version. 12 | # 13 | # This program is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU Lesser General Public 19 | # License along with this program; if not, write to the Free Software 20 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 21 | # MA 02110-1301, USA. 22 | # 23 | # 24 | ################################################################################### 25 | 26 | import collections 27 | 28 | 29 | def get_val_from_range(lookup, input, interpolate=False): 30 | """ 31 | lookup: dictionary 32 | input: float 33 | 34 | For working principle, see below tests 35 | """ 36 | lookup_sorted = collections.OrderedDict( 37 | sorted(lookup.items(), key=lambda t: float(t[0])) 38 | ) 39 | val = None 40 | prev_val = None 41 | prev_key = None 42 | input = float(input) 43 | for _range in lookup_sorted: 44 | val = float(lookup_sorted[_range]) 45 | if input > float(_range): 46 | prev_val = val 47 | prev_key = float(_range) 48 | continue 49 | 50 | if interpolate: 51 | # Do the interpolation here 52 | if prev_key is not None: 53 | key = float(_range) 54 | # print "interpolate for input: ", input, ": ", prev_key, "to ", key, "->", prev_val, val 55 | input_offset_percentage = (input - prev_key) / (key - prev_key) 56 | val_diff = val - prev_val 57 | val_offset = val_diff * input_offset_percentage 58 | interpolated_val = prev_val + val_offset 59 | round_2 = lambda a: int((a * 100) + 0.5) / 100.0 60 | val = round_2(interpolated_val) 61 | # print "...interpolated to: ", val, interpolated_val 62 | break 63 | return val 64 | 65 | 66 | mytable = {1: 0.25, 1.1: 0.28, 3: 0.33, 5: 0.42, 7: 0.5} 67 | 68 | # Interpolation disabled 69 | assert get_val_from_range(mytable, 0.1) == 0.25 70 | assert get_val_from_range(mytable, 0.99) == 0.25 71 | assert get_val_from_range(mytable, 1) == 0.25 72 | assert get_val_from_range(mytable, 1.01) == 0.28 73 | assert get_val_from_range(mytable, 1.09) == 0.28 74 | assert get_val_from_range(mytable, 1.2) == 0.33 75 | assert get_val_from_range(mytable, 2.5) == 0.33 76 | assert get_val_from_range(mytable, 4) == 0.42 77 | assert get_val_from_range(mytable, 40) == 0.5 78 | assert get_val_from_range(mytable, 1000) == 0.5 79 | 80 | # Interpolation enabled 81 | assert get_val_from_range(mytable, 0.1, True) == 0.25 82 | assert get_val_from_range(mytable, 0.99, True) == 0.25 83 | assert get_val_from_range(mytable, 1, True) == 0.25 84 | assert get_val_from_range(mytable, 1.01, True) == 0.25 85 | assert get_val_from_range(mytable, 1.09, True) == 0.28 86 | assert get_val_from_range(mytable, 2.05, True) == 0.31 87 | assert get_val_from_range(mytable, 2.5, True) == 0.32 88 | assert get_val_from_range(mytable, 4, True) == 0.38 89 | assert get_val_from_range(mytable, 6, True) == 0.46 90 | assert get_val_from_range(mytable, 40, True) == 0.5 91 | assert get_val_from_range(mytable, 1000, True) == 0.5 92 | -------------------------------------------------------------------------------- /package.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | SheetMetal Workbench 4 | A simple sheet metal tools workbench for FreeCAD. 5 | 0.7.22 6 | 2025-03-18 7 | Shai Seger 8 | LGPL-2.1-or-later 9 | https://github.com/shaise/FreeCAD_SheetMetal 10 | https://github.com/shaise/FreeCAD_SheetMetal/blob/master/README.md 11 | Resources/icons/SMLogo.svg 12 | 13 | 14 | 15 | SMWorkbench 16 | ./ 17 | sheetmetal 18 | networkx 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /smwb_locator.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | ############################################################################## 3 | # 4 | # smwb_locator.py 5 | # 6 | # Copyright 2015 Shai Seger 7 | # 8 | # This program is free software; you can redistribute it and/or 9 | # modify it under the terms of the GNU Lesser General Public 10 | # License as published by the Free Software Foundation; either 11 | # version 2 of the License, or (at your option) any later version. 12 | # 13 | # This program is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU Lesser General Public 19 | # License along with this program; if not, write to the Free Software 20 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 21 | # MA 02110-1301, USA. 22 | # 23 | # 24 | ############################################################################## 25 | -------------------------------------------------------------------------------- /tools/Press_brake_schematic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 22 | 24 | image/svg+xml 25 | 27 | 28 | 29 | 30 | 50 | 52 | 59 | 65 | 70 | 71 | 73 | 77 | 81 | 82 | 91 | 100 | 109 | 110 | 113 | 117 | 121 | 125 | 129 | 134 | 139 | 144 | 149 | PUNCH 159 | WORKPIECE 169 | BACK GAUGE 179 | DIE 189 | 190 | 191 | -------------------------------------------------------------------------------- /tools/README.md: -------------------------------------------------------------------------------- 1 | # Air Bending 2 | 3 | ![air-bending-punch-distances](./air-bending-punch-distances.svg) 4 | 5 | 6 | ``` 7 | <- b -> <- a -> 8 | ----+------+-------| 9 | B A 10 | ``` 11 | 12 | ## First bend (A) 13 | 14 | x is equal to the "ML" distance, which is "a" in this case. 15 | 16 | ## After first bend (B, ...) 17 | 18 | x is equal to "b + FD" distance. Calculate FD value with [calc-unfold](./calc-unfold.py) tool. 19 | 20 | ## K-factor table 21 | 22 | Taken from https://en.wikipedia.org/wiki/Bending_(metalworking): 23 | 24 | | Generic K-factors (ANSI) | Aluminum | Aluminum | Steel | 25 | |----------------------------|----------------|------------------|----------------| 26 | | Radius | Soft materials | Medium materials | Hard materials | 27 | | **Air bending** | | | | 28 | | 0 to thickness | 0.33 | 0.38 | 0.40 | 29 | | Thickness to 3 × thickness | 0.40 | 0.43 | 0.45 | 30 | | Greater than 3 × thickness | 0.50 | 0.50 | 0.50 | 31 | | **Bottoming** | | | | 32 | | 0 to thickness | 0.42 | 0.44 | 0.46 | 33 | | Thickness to 3 × thickness | 0.46 | 0.47 | 0.48 | 34 | | Greater than 3 × thickness | 0.50 | 0.50 | 0.50 | 35 | | **Coining** | | | | 36 | | 0 to thickness | 0.38 | 0.41 | 0.44 | 37 | | Thickness to 3 × thickness | 0.44 | 0.46 | 0.47 | 38 | | Greater than 3 × thickness | 0.50 | 0.50 | 0.50 | 39 | -------------------------------------------------------------------------------- /tools/calc-unfold.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # Tool for manually calculating the unfolded geometry 3 | # See terminology.png for terminology. 4 | # -*- coding: utf-8 -*- 5 | ################################################################################### 6 | # 7 | # calc-unfold.py 8 | # 9 | # Copyright 2019 Cerem Cem Aslan 10 | # Copyright 2023 Ondsel Onc. 11 | # 12 | # This program is free software; you can redistribute it and/or 13 | # modify it under the terms of the GNU Lesser General Public 14 | # License as published by the Free Software Foundation; either 15 | # version 2 of the License, or (at your option) any later version. 16 | # 17 | # This program is distributed in the hope that it will be useful, 18 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 | # GNU General Public License for more details. 21 | # 22 | # You should have received a copy of the GNU Lesser General Public 23 | # License along with this program; if not, write to the Free Software 24 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 25 | # MA 02110-1301, USA. 26 | # 27 | # 28 | ################################################################################### 29 | 30 | import math 31 | 32 | r = inner_radius = 1.64 33 | T = thickness = 2.0 34 | ML = mold_line_distance = 50 35 | K = k_factor = 0.38 36 | bend_angle = 90.0 37 | 38 | t = thickness * k_factor 39 | BA = bend_allowance = 2.0 * math.pi * (r + t) * (bend_angle / 360.0) 40 | leg_length = ML - BA / 2.0 41 | ossb = r + T 42 | flange_diff = ossb - BA / 2.0 43 | flange_length = ossb + leg_length 44 | 45 | 46 | print("Inputs: r, T, Kf, ML, angle") 47 | print("---------------------------------") 48 | print(f"Effective inner radius: {inner_radius:.2f} mm") 49 | print(f"Effective outer radius: {(r + T):.2f} mm") 50 | print(f"Flange length: {flange_length:.2f} mm") 51 | print(f"* Flange diff (FD = FL - ML): {flange_diff:.2f} mm") 52 | print(f"Bend allowance: {BA:.2f} mm") 53 | print(f"Leg length: {leg_length:.2f} mm") 54 | -------------------------------------------------------------------------------- /tools/terminology.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaise/FreeCAD_SheetMetal/21ee403ab369cc2d08de0dcc989cb1bfd201773d/tools/terminology.png -------------------------------------------------------------------------------- /updating-ui.md: -------------------------------------------------------------------------------- 1 | # Updating UnfoldOptions.ui 2 | 3 | 1. Generate the Python code from UnfoldOptions.ui by: 4 | 5 | ``` 6 | pyuic4 UnfoldOptions.ui -o myui.py 7 | ``` 8 | 9 | 2. Copy and paste relevant codes into the `SMUnfoldTaskPanel()` class in `SheetMetalUnfolder.py`. 10 | 11 | 12 | # TODO 13 | 14 | * Use `UnfoldOptions.ui` file directly. 15 | --------------------------------------------------------------------------------