├── freecad
└── frametools
│ ├── version.py
│ ├── __init__.py
│ ├── init_gui.py
│ ├── miter-cut.ui
│ ├── plane-cut.ui
│ ├── shape-cut.ui
│ ├── boxtools.py
│ ├── commands.py
│ ├── screw_maker.py
│ ├── bspline_tools.py
│ ├── make_beam.ui
│ ├── fem2d.py
│ ├── icons
│ ├── nurbs_connect.svg
│ ├── beam.svg
│ ├── beam_plane_cut.svg
│ ├── linked_face.svg
│ ├── reload.svg
│ ├── beam_miter_cut.svg
│ ├── screw.svg
│ ├── beam_shape_cut.svg
│ └── generic_solver.svg
│ ├── beamobj.py
│ └── interaction.py
├── MANIFEST.in
├── record_frame.gif
├── example
├── fahrrad.fcstd
└── gate
│ ├── gate.FCStd
│ ├── beams_cut.png
│ ├── drawing.png
│ ├── inner_beams.png
│ ├── outer_beams.png
│ ├── beams_sketch.png
│ ├── profile_sketch.png
│ └── outer_beams_cut.png
├── notebooks
└── .ipynb_checkpoints
│ └── Untitled-checkpoint.ipynb
├── setup.py
├── .gitignore
├── package.xml
└── README.md
/freecad/frametools/version.py:
--------------------------------------------------------------------------------
1 | __version__ = "0.1.0"
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | recursive-include freecad/frametools/icons *
--------------------------------------------------------------------------------
/record_frame.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/looooo/freecad.frametools/HEAD/record_frame.gif
--------------------------------------------------------------------------------
/example/fahrrad.fcstd:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/looooo/freecad.frametools/HEAD/example/fahrrad.fcstd
--------------------------------------------------------------------------------
/example/gate/gate.FCStd:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/looooo/freecad.frametools/HEAD/example/gate/gate.FCStd
--------------------------------------------------------------------------------
/example/gate/beams_cut.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/looooo/freecad.frametools/HEAD/example/gate/beams_cut.png
--------------------------------------------------------------------------------
/example/gate/drawing.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/looooo/freecad.frametools/HEAD/example/gate/drawing.png
--------------------------------------------------------------------------------
/example/gate/inner_beams.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/looooo/freecad.frametools/HEAD/example/gate/inner_beams.png
--------------------------------------------------------------------------------
/example/gate/outer_beams.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/looooo/freecad.frametools/HEAD/example/gate/outer_beams.png
--------------------------------------------------------------------------------
/example/gate/beams_sketch.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/looooo/freecad.frametools/HEAD/example/gate/beams_sketch.png
--------------------------------------------------------------------------------
/example/gate/profile_sketch.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/looooo/freecad.frametools/HEAD/example/gate/profile_sketch.png
--------------------------------------------------------------------------------
/example/gate/outer_beams_cut.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/looooo/freecad.frametools/HEAD/example/gate/outer_beams_cut.png
--------------------------------------------------------------------------------
/notebooks/.ipynb_checkpoints/Untitled-checkpoint.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [],
3 | "metadata": {},
4 | "nbformat": 4,
5 | "nbformat_minor": 2
6 | }
7 |
--------------------------------------------------------------------------------
/freecad/frametools/__init__.py:
--------------------------------------------------------------------------------
1 | import os
2 | from .version import __version__
3 | DIR = os.path.dirname(__file__)
4 | ICON_PATH = os.path.join(DIR, "icons")
5 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | from setuptools import setup
2 | import sys, os
3 |
4 | version_path = os.path.join(os.path.abspath(os.path.dirname(__file__)),
5 | "freecad", "frametools", "version.py")
6 | with open(version_path) as fp:
7 | exec(fp.read())
8 |
9 | setup(name='freecad.frametools',
10 | version=__version__,
11 | packages=['freecad',
12 | 'freecad.frametools'],
13 | maintainer="looooo",
14 | maintainer_email="sppedflyer@gmail.com",
15 | url="https://github.com/looooo/pip-integration",
16 | description="some frame-tools for freecad",
17 | install_requires=[],
18 | include_package_data=True)
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 |
5 | # C extensions
6 | *.so
7 |
8 | # Distribution / packaging
9 | .Python
10 | env/
11 | build/
12 | develop-eggs/
13 | dist/
14 | downloads/
15 | eggs/
16 | .eggs/
17 | lib/
18 | lib64/
19 | parts/
20 | sdist/
21 | var/
22 | *.egg-info/
23 | .installed.cfg
24 | *.egg
25 |
26 | # PyInstaller
27 | # Usually these files are written by a python script from a template
28 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
29 | *.manifest
30 | *.spec
31 |
32 | # Installer logs
33 | pip-log.txt
34 | pip-delete-this-directory.txt
35 |
36 | # Unit test / coverage reports
37 | htmlcov/
38 | .tox/
39 | .coverage
40 | .coverage.*
41 | .cache
42 | nosetests.xml
43 | coverage.xml
44 | *,cover
45 |
46 | # Translations
47 | *.mo
48 | *.pot
49 |
50 | # Django stuff:
51 | *.log
52 |
53 | # Sphinx documentation
54 | docs/_build/
55 |
56 | # PyBuilder
57 | target/
58 |
--------------------------------------------------------------------------------
/package.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Frame and beams workbench
5 |
6 | A workbench for beams and frames
7 |
8 | 0.1.1
9 |
10 | 2023-08-08
11 |
12 | looooo
13 |
14 | LGPL-2.1-or-later
15 |
16 | https://github.com/looooo/freecad.frametools
17 |
18 | https://github.com/looooo/freecad.frametools/issues
19 |
20 | https://github.com/looooo/freecad.frametools/blob/master/README.md
21 |
22 | https://github.com/looooo/freecad.frametools
23 |
24 | freecad/frametools/icons/beam.svg
25 |
26 |
27 |
28 | 0.19.0
29 | scipy
30 | frame
31 | beam
32 | FrameWorkbench
33 | ./
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/freecad/frametools/init_gui.py:
--------------------------------------------------------------------------------
1 | try:
2 | import FreeCADGui as Gui
3 | import FreeCAD
4 | except ImportError:
5 | print("module not loaded with freecad")
6 |
7 | import os
8 | from freecad.frametools import ICON_PATH
9 |
10 | class FrameWorkbench(Gui.Workbench):
11 | '''frame workbench'''
12 |
13 | MenuText = 'Frame and beams'
14 | ToolTip = 'Frame and beams'
15 | Icon = os.path.join(ICON_PATH, 'beam.svg')
16 | toolbox = ['Beam', 'CutMiter', 'CutPlane', 'CutShape', 'Reload']
17 | boxbox = ['LinkedFace', 'ExtrudedFace', 'FlatFace', 'FemSolver', 'ScrewMaker']
18 |
19 | def GetClassName(self):
20 | return 'Gui::PythonWorkbench'
21 |
22 | def Initialize(self):
23 |
24 | from freecad.frametools import commands
25 | Gui.addCommand('Beam', commands.Beam())
26 | Gui.addCommand('CutMiter', commands.CutMiter())
27 | Gui.addCommand('CutPlane', commands.CutPlane())
28 | Gui.addCommand('CutShape', commands.CutShape())
29 | Gui.addCommand('Reload', commands.Reload())
30 | Gui.addCommand('LinkedFace', commands.LinkedFace())
31 | Gui.addCommand('ExtrudedFace', commands.ExtrudedFace())
32 | Gui.addCommand('FlatFace', commands.FlatFace())
33 | Gui.addCommand('FlatFace', commands.NurbsConnection())
34 | Gui.addCommand('FemSolver', commands.FemSolver())
35 | Gui.addCommand('ScrewMaker', commands.ScrewMaker())
36 |
37 | self.appendToolbar('Frame', self.toolbox)
38 | self.appendMenu('Frame', self.toolbox)
39 | self.appendToolbar('Box', self.boxbox)
40 | self.appendMenu('Box', self.boxbox)
41 |
42 | def Activated(self):
43 | pass
44 |
45 | def Deactivated(self):
46 | pass
47 |
48 | Gui.addWorkbench(FrameWorkbench())
49 |
--------------------------------------------------------------------------------
/freecad/frametools/miter-cut.ui:
--------------------------------------------------------------------------------
1 |
2 |
3 | FrameAndBeamGui::CutBeamsParameters
4 |
5 |
6 |
7 | 0
8 | 0
9 | 271
10 | 604
11 |
12 |
13 |
14 | Cut type
15 |
16 |
17 | -
18 |
19 |
-
20 |
21 |
22 | Beam 1
23 |
24 |
25 | true
26 |
27 |
28 |
29 | -
30 |
31 |
32 | true
33 |
34 |
35 |
36 | -
37 |
38 |
39 | Beam 2
40 |
41 |
42 | true
43 |
44 |
45 |
46 | -
47 |
48 |
49 | true
50 |
51 |
52 |
53 | -
54 |
55 |
56 | Cut
57 |
58 |
59 |
60 | -
61 |
62 |
63 | true
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
--------------------------------------------------------------------------------
/freecad/frametools/plane-cut.ui:
--------------------------------------------------------------------------------
1 |
2 |
3 | FrameAndBeamGui::CutBeamsParameters
4 |
5 |
6 |
7 | 0
8 | 0
9 | 271
10 | 604
11 |
12 |
13 |
14 | Cut type
15 |
16 |
17 | -
18 |
19 |
-
20 |
21 |
22 | Beam to cut
23 |
24 |
25 | true
26 |
27 |
28 |
29 | -
30 |
31 |
32 | true
33 |
34 |
35 |
36 | -
37 |
38 |
39 | Cut with
40 |
41 |
42 | true
43 |
44 |
45 |
46 | -
47 |
48 |
49 | true
50 |
51 |
52 |
53 | -
54 |
55 |
56 | Cut
57 |
58 |
59 |
60 | -
61 |
62 |
63 | true
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
--------------------------------------------------------------------------------
/freecad/frametools/shape-cut.ui:
--------------------------------------------------------------------------------
1 |
2 |
3 | FrameAndBeamGui::CutBeamsParameters
4 |
5 |
6 |
7 | 0
8 | 0
9 | 271
10 | 604
11 |
12 |
13 |
14 | Cut type
15 |
16 |
17 | -
18 |
19 |
-
20 |
21 |
22 | Beam to cut
23 |
24 |
25 | true
26 |
27 |
28 |
29 | -
30 |
31 |
32 | true
33 |
34 |
35 |
36 | -
37 |
38 |
39 | Cut beam with
40 |
41 |
42 | true
43 |
44 |
45 |
46 | -
47 |
48 |
49 | true
50 |
51 |
52 |
53 | -
54 |
55 |
56 | Cut
57 |
58 |
59 |
60 | -
61 |
62 |
63 | true
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
--------------------------------------------------------------------------------
/freecad/frametools/boxtools.py:
--------------------------------------------------------------------------------
1 | import FreeCAD as App
2 | import FreeCADGui as Gui
3 | import Part
4 | import numpy as np
5 |
6 | def create_extruded_face():
7 | sel = Gui.Selection.getSelectionEx()[0]
8 | base = sel.Object
9 | name = sel.SubElementNames[0]
10 | thickness = 1
11 | feature = App.ActiveDocument.addObject("Part::FeaturePython", "Extruded_" + name)
12 | ExtrudedFace(feature, base, name, thickness)
13 | feature.ViewObject.Proxy = 0
14 | App.ActiveDocument.recompute()
15 |
16 |
17 | def create_linked_face():
18 | sel = Gui.Selection.getSelectionEx()[0]
19 | base = sel.Object
20 | name = sel.SubElementNames[0]
21 | feature = App.ActiveDocument.addObject("Part::FeaturePython", "Linked_" + name)
22 | LinkedFace(feature, base, name)
23 | feature.ViewObject.Proxy = 0
24 | App.ActiveDocument.recompute()
25 |
26 |
27 | def create_flat_face():
28 | base = Gui.Selection.getSelection()[0]
29 | feature = App.ActiveDocument.addObject("Part::FeaturePython", "Flat_" + base.Name)
30 | FlatFace(feature, base)
31 | feature.ViewObject.Proxy = 0
32 | App.ActiveDocument.recompute()
33 |
34 |
35 | class ExtrudedFace(object):
36 | def __init__(self, obj, base, name, thickness):
37 | self.obj = obj
38 | obj.Proxy = self
39 | obj.addProperty("App::PropertyLink", "base", "not yet", "docs").base = base
40 | obj.addProperty("App::PropertyString", "name", "not yet", "docs").name = name
41 | obj.addProperty("App::PropertyFloat", "thickness", "not yet", "docs").thickness = thickness
42 |
43 | def execute(self, fp):
44 | # check if it is a face:
45 | face = None
46 | try:
47 | face = fp.base.Shape.getElement(fp.name)
48 | except AttributeError as e:
49 | Warning(e)
50 | print(face)
51 | if face and isinstance(face, Part.Face):
52 | fp.Shape = face.extrude(face.normalAt(0, 0) * fp.thickness)
53 |
54 | def __getstate__(self):
55 | return None
56 |
57 | def __setstate__(self, state):
58 | return None
59 |
60 |
61 | class LinkedFace(object):
62 | def __init__(self, obj, base, name):
63 | self.obj = obj
64 | obj.Proxy = self
65 | obj.addProperty("App::PropertyLink", "base", "group", "docs").base = base
66 | obj.addProperty("App::PropertyString", "name", "group", "docs").name = name
67 |
68 | def execute(self, fp):
69 | # check if it is a face:
70 | face = None
71 | try:
72 | face = fp.base.Shape.getElement(fp.name)
73 | except AttributeError as e:
74 | Warning(e)
75 | print(face)
76 | if face and isinstance(face, Part.Face):
77 | fp.Shape = face
78 |
79 | def __getstate__(self):
80 | return None
81 |
82 | def __setstate__(self, state):
83 | return None
84 |
85 | class FlatFace(object):
86 | def __init__(self, obj, base):
87 | self.obj = obj
88 | obj.Proxy = self
89 | obj.addProperty("App::PropertyLink", "base", "group", "docs").base = base
90 |
91 | def execute(self, fp):
92 | # get face with biggest area
93 | area = 0
94 | for i, face in enumerate(fp.base.Shape.Faces):
95 | if area < face.Area:
96 | item_nr = i
97 | area = face.Area
98 | # assume a refined shape
99 | face = Part.Face(fp.base.Shape.Faces[item_nr])
100 |
101 | normal = face.normalAt(0, 0)
102 | rot = App.Rotation(normal, App.Vector(0, 0, 1))
103 | placement = App.Placement(App.Vector(), rot)
104 | face = face.transformGeometry(placement.toMatrix())
105 | trans = App.Vector(0, 0, 0) - face.Faces[0].valueAt(0, 0)
106 | placement = App.Placement(trans, App.Rotation())
107 | face = face.transformGeometry(placement.toMatrix())
108 | fp.Shape = Part.Compound(face.Wires)
109 |
110 | def __getstate__(self):
111 | return None
112 |
113 | def __setstate__(self, state):
114 | return None
115 |
--------------------------------------------------------------------------------
/freecad/frametools/commands.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | import FreeCADGui as Gui
4 | import FreeCAD as App
5 | from freecad.frametools import ICON_PATH
6 | from . import interaction, boxtools, bspline_tools
7 | from . import fem2d
8 | from . import screw_maker
9 |
10 |
11 | __all__ = [
12 | "Beam",
13 | "CutMiter",
14 | "CutPlane",
15 | "CutShape"]
16 |
17 | class BaseCommand(object):
18 |
19 | def __init__(self):
20 | pass
21 |
22 | def GetResources(self):
23 | return {'Pixmap': '.svg', 'MenuText': 'Text', 'ToolTip': 'Text'}
24 |
25 | def IsActive(self):
26 | if App.ActiveDocument is None:
27 | return False
28 | else:
29 | return True
30 |
31 | def Activated(self):
32 | pass
33 |
34 | @property
35 | def view(self):
36 | return Gui.ActiveDocument.ActiveView
37 |
38 |
39 | class Beam(BaseCommand):
40 |
41 | def Activated(self):
42 | interaction.make_beam(self.view)
43 |
44 | def GetResources(self):
45 | return {'Pixmap': os.path.join(ICON_PATH, 'beam.svg'), 'MenuText': 'Beam', 'ToolTip': 'Create a beam'}
46 |
47 |
48 | class CutMiter(BaseCommand):
49 |
50 | def Activated(self):
51 | interaction.make_miter_cut(self.view)
52 |
53 | def GetResources(self):
54 | return {'Pixmap': os.path.join(ICON_PATH, 'beam_miter_cut.svg'), 'MenuText': 'Miter Cut', 'ToolTip': 'Perform miter cut of 2 beams'}
55 |
56 |
57 | class CutPlane(BaseCommand):
58 |
59 | def Activated(self):
60 | interaction.make_plane_cut(self.view)
61 |
62 | def GetResources(self):
63 | return {'Pixmap': os.path.join(ICON_PATH, 'beam_plane_cut.svg'), 'MenuText': 'Plane Cut', 'ToolTip': 'Cut a beam by a face of another beam'}
64 |
65 |
66 | class CutShape(BaseCommand):
67 |
68 | def Activated(self):
69 | interaction.make_shape_cut(self.view)
70 |
71 | def GetResources(self):
72 | return {'Pixmap': os.path.join(ICON_PATH, 'beam_shape_cut.svg'), 'MenuText': 'Shape Cut', 'ToolTip': 'Cut a beam by outer surface of another beam'}
73 |
74 |
75 | class LinkedFace(BaseCommand):
76 |
77 | def Activated(self):
78 | boxtools.create_linked_face()
79 |
80 | def GetResources(self):
81 | return {'Pixmap': os.path.join(ICON_PATH, 'linked_face.svg'), 'MenuText': 'Linked Face', 'ToolTip': 'linked_face'}
82 |
83 |
84 | class ExtrudedFace(BaseCommand):
85 |
86 | def Activated(self):
87 | boxtools.create_extruded_face()
88 |
89 | def GetResources(self):
90 | return {'Pixmap': os.path.join(ICON_PATH, 'extruded_face.svg'), 'MenuText': 'Extruded Face', 'ToolTip': 'extruded_face'}
91 |
92 |
93 | class FlatFace(BaseCommand):
94 |
95 | def Activated(self):
96 | boxtools.create_flat_face()
97 |
98 | def GetResources(self):
99 | return {'Pixmap': os.path.join(ICON_PATH, 'linked_face.svg'), 'MenuText': 'Flat Face', 'ToolTip': 'flat_face'}
100 |
101 | class ScrewMaker(BaseCommand):
102 |
103 | def Activated(self):
104 | a = App.ActiveDocument.addObject("Part::FeaturePython", "screw")
105 | screw_maker.Screw(a)
106 | screw_maker.ViewproviderScrew(a.ViewObject)
107 |
108 | def GetResources(self):
109 | return {'Pixmap': os.path.join(ICON_PATH, 'screw.svg'), 'MenuText': 'create Screw', 'ToolTip': 'create Screw'}
110 |
111 |
112 |
113 | class NurbsConnection(BaseCommand):
114 |
115 | def Activated(self):
116 | bspline_tools.make_nurbs_connection()
117 |
118 | def GetResources(self):
119 | return {'Pixmap': os.path.join(ICON_PATH, 'nurbs_connect.svg'), 'MenuText': 'NURBS Connect', 'ToolTip': 'nurbs_connect'}
120 |
121 |
122 | class FemSolver(BaseCommand):
123 |
124 | def Activated(self):
125 | sel = Gui.Selection.getSelection()
126 | fem2d.make_GenericSolver(sel[0], sel[1])
127 | App.ActiveDocument.recompute()
128 |
129 | def GetResources(self):
130 | return {'Pixmap': os.path.join(ICON_PATH, "generic_solver.svg"), 'MenuText': 'FEM Solver', 'ToolTip': 'fem_solver'}
131 |
132 |
133 | class Reload():
134 | NOT_RELOAD = ["freecad.frametools.init_gui"]
135 | RELOAD = ["freecad.frametools"]
136 | def GetResources(self):
137 | return {'Pixmap': os.path.join(ICON_PATH, 'reload.svg'), 'MenuText': 'Refresh', 'ToolTip': 'Refresh'}
138 |
139 | def IsActive(self):
140 | return True
141 |
142 | def Activated(self):
143 | try:
144 | from importlib import reload
145 | except ImportError:
146 | pass # this is python2
147 | import sys
148 | for name, mod in sys.modules.copy().items():
149 | for rld in self.RELOAD:
150 | if rld in name:
151 | if mod and name not in self.NOT_RELOAD:
152 | print('reload {}'.format(name))
153 | reload(mod)
154 | from pivy import coin
155 |
--------------------------------------------------------------------------------
/freecad/frametools/screw_maker.py:
--------------------------------------------------------------------------------
1 | import os
2 | import numpy as np
3 | import FreeCAD as App
4 | from Part import BSplineCurve, Wire, Face
5 | import Part
6 | import copy
7 |
8 | class Screw(object):
9 | def __init__(self, obj):
10 | '''Screw'''
11 | print("was soll das")
12 | obj.addProperty("App::PropertyInteger", "threads", "screw properties", "number of threads").threads = 1
13 | obj.addProperty("App::PropertyLink","profile","screw properties","sketch profile of section")
14 | obj.addProperty("App::PropertyLength", "height", "screw properties", "total height").height = 1
15 | obj.addProperty("App::PropertyBool", "left_hand", "screw properties", "rotation").left_hand = False
16 | obj.Proxy = self
17 | self.Object = obj
18 |
19 | def execute(self, obj):
20 | if not obj.profile:
21 | return
22 | # 1: get all edges
23 | edges = obj.profile.Shape.Edges
24 | # 2: discretize edges:
25 | edges_discretized = []
26 | for e in edges:
27 | edges_discretized.append(np.array(e.discretize(100)))
28 | # assume that the profile is in xz-plane (rz)
29 | # so the helical projection is in the xy-plane (z=0)
30 | # the z-axis is the direction of the helix
31 | # 3: height (pitch) of profile
32 | height = 0
33 | for ed in edges_discretized:
34 | height = max([height, max(ed.T[2])])
35 | pitch = obj.threads * height
36 | all_edges_discretized = [edges_discretized]
37 | for i in range(1, obj.threads):
38 | segment_edges = []
39 | for ed in edges_discretized:
40 | ed_new = copy.deepcopy(ed)
41 | ed_new.T[2] += height * i
42 | segment_edges.append(ed_new)
43 | all_edges_discretized.append(segment_edges)
44 | # 4: do the helical projection
45 | segments = []
46 | for segment in all_edges_discretized:
47 | profile_coords = []
48 | for ed in segment:
49 | profile_coords.append(helical_projection(ed.T[0], ed.T[2], pitch, obj.left_hand))
50 | segments.append(profile_coords)
51 | # 5: create the bspline interpolation
52 | wires = []
53 | for s in segments:
54 | wires.append(make_bspline_wire(s))
55 | try:
56 | wire = Wire(wires)
57 | face = Face(wire)
58 | except Part.OCCError:
59 | # we have multiple faces
60 | face = Face(wires)
61 | sign = -(int(obj.left_hand) * 2 - 1)
62 | obj.Shape = helicalextrusion(face, obj.height, sign * obj.height / pitch * 2 * np.pi)
63 |
64 | def __getstate__(self):
65 | return None
66 |
67 | def __setstate__(self, state):
68 | return None
69 |
70 | def attach(self):
71 | pass
72 |
73 | class ViewproviderScrew(object):
74 | def __init__(self, obj):
75 | ''' Set this object to the proxy object of the actual view provider '''
76 | obj.Proxy = self
77 | self.vobj = obj
78 |
79 | def attach(self, vobj):
80 | self.vobj = vobj
81 |
82 | def claimChildren(self):
83 | return [self.vobj.Object.profile]
84 |
85 | def getIcon(self):
86 | _dir = os.path.dirname(os.path.realpath(__file__))
87 | return(_dir + "/" + "icons/screw.svg")
88 |
89 | def __getstate__(self):
90 | return None
91 |
92 | def __setstate__(self, state):
93 | return None
94 |
95 |
96 |
97 | def helical_projection(r, z, pitch, left_hand):
98 | """
99 | pitch: height of one helical revolution
100 | r: radius of points
101 | z: heihgt of points
102 | """
103 | sign = int(left_hand) * 2 - 1 # 1 * 2 -1 = 1, 0 * 2 - 1 = -1
104 | phi = 2 * sign * np.pi * z / pitch
105 | x = r * np.cos(phi)
106 | y = r * np.sin(phi)
107 | z = 0 * y
108 | return np.array([x, y, 0 * y]). T
109 |
110 | def make_bspline_wire(pts):
111 | wi = []
112 | for i in pts:
113 | out = BSplineCurve()
114 | out.interpolate(list(map(fcvec, i)))
115 | wi.append(out.toShape())
116 | return Wire(wi)
117 |
118 | def fcvec(x):
119 | if len(x) == 2:
120 | return(App.Vector(x[0], x[1], 0))
121 | else:
122 | return(App.Vector(x[0], x[1], x[2]))
123 |
124 |
125 | def helicalextrusion(face, height, angle):
126 | """
127 | A helical extrusion using the BRepOffsetAPI
128 | face -- the face to extrude (may contain holes, i.e. more then one wires)
129 | height -- the height of the extrusion, normal to the face
130 | angle -- the twist angle of the extrusion in radians
131 |
132 | returns a solid
133 | """
134 | pitch = height * 2 * np.pi / abs(angle)
135 | radius = 10.0 # as we are only interested in the "twist", we take an arbitrary constant here
136 | cone_angle = 0
137 | direction = bool(angle < 0)
138 | spine = Part.makeHelix(pitch, height, radius, cone_angle, direction)
139 | def make_pipe(path, profile):
140 | """
141 | returns (shell, last_wire)
142 | """
143 | mkPS = Part.BRepOffsetAPI.MakePipeShell(path)
144 | mkPS.setFrenetMode(True) # otherwise, the profile's normal would follow the path
145 | mkPS.add(profile, False, False)
146 | mkPS.build()
147 | return (mkPS.shape(), mkPS.lastShape())
148 | shell_faces = []
149 | top_wires = []
150 | for wire in face.Wires:
151 | pipe_shell, top_wire = make_pipe(spine, wire)
152 | shell_faces.extend(pipe_shell.Faces)
153 | top_wires.append(top_wire)
154 | top_face = Part.Face(top_wires)
155 | shell_faces.append(top_face)
156 | shell_faces.append(face) # the bottom is what we extruded
157 | shell = Part.makeShell(shell_faces)
158 | #shell.sewShape() # fill gaps that may result from accumulated tolerances. Needed?
159 | #shell = shell.removeSplitter() # refine. Needed?
160 | return Part.makeSolid(shell)
161 |
162 | def make_face(edge1, edge2):
163 | v1, v2 = edge1.Vertexes
164 | v3, v4 = edge2.Vertexes
165 | e1 = Wire(edge1)
166 | e2 = LineSegment(v1.Point, v3.Point).toShape().Edges[0]
167 | e3 = edge2
168 | e4 = LineSegment(v4.Point, v2.Point).toShape().Edges[0]
169 | w = Wire([e3, e4, e1, e2])
170 | return(Face(w))
171 |
--------------------------------------------------------------------------------
/freecad/frametools/bspline_tools.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | import Part
3 | import FreeCADGui as gui
4 | try:
5 | from scipy.optimize import least_squares
6 | except:
7 | least_squares = None
8 |
9 |
10 | def curve_between_lines(p0, p1, v0, v1, n=None):
11 | if n is None:
12 | n = np.cross(v0, v1)
13 | n /= np.linalg.norm(n)
14 | def project_to_plane(p):
15 | return p - p.dot(n) * n
16 | tang0 = -v0 / np.linalg.norm(v0)
17 | tang1 = -v1 / np.linalg.norm(v1)
18 | knots = np.array([0., 1.])
19 | mults = np.array([4, 4])
20 | p0_proj, p1_proj, t0_proj, t1_proj = list(map(project_to_plane, [p0, p1, tang0, tang1]))
21 | s0, s1 = abs(np.linalg.lstsq(np.array([t0_proj, t1_proj]).T, p1_proj - p0_proj)[0])
22 | print(s0, s1)
23 | w0 = 0.56265951
24 | t00 = 0.65863639
25 | n0 = np.cross(n, t0_proj)
26 | n1 = np.cross(n, t1_proj)
27 | sn0 , sn1 = np.linalg.lstsq(np.array([n0, n1]).T, p1_proj - p0_proj)[0]
28 | center = p0_proj + sn0 * n0
29 | def min_function(params):
30 | w0, t00 = params
31 | t0 = t00 * abs(s0)
32 | t1 = t00 * abs(s1)
33 | weights = np.array([1., w0, w0, 1.])
34 | poles = np.array([p0_proj, p0_proj + t0 * t0_proj,
35 | p1_proj + t1 * t1_proj, p1_proj])
36 | r = (np.linalg.norm(center - p0_proj) + np.linalg.norm(center - p1_proj)) / 2.
37 | bs = Part.BSplineCurve()
38 | bs.buildFromPolesMultsKnots(poles, mults, knots)
39 | for i, wi in enumerate(weights):
40 | bs.setWeight(i + 1, wi)
41 | dist = (np.array(bs.discretize(100)) - center) / r
42 | return sum((np.linalg.norm(dist, axis=1) - 1)**2)
43 | if not least_squares is None:
44 | w0, t00 = least_squares(min_function, [w0, t00]).x
45 | t0 = t00 * abs(s0)
46 | t1 = t00 * abs(s1)
47 | weights = np.array([1, w0, w0, 1])
48 | poles = np.array([p0, p0 + t0 * tang0, p1 + t1 * tang1, p1])
49 | bs = Part.BSplineCurve()
50 | bs.buildFromPolesMultsKnots(poles, mults, knots)
51 | for i, wi in enumerate(weights):
52 | bs.setWeight(i + 1, wi)
53 | return bs
54 |
55 |
56 | def make_nurbs_connection():
57 | edge1 = gui.Selection.getSelectionEx()[0].SubObjects[0]
58 | edge2 = gui.Selection.getSelectionEx()[1].SubObjects[0]
59 | try:
60 | normal = gui.Selection.getSelectionEx()[2].SubObjects[0]
61 | except Exception:
62 | normal = None
63 | p11 = np.array(edge1.valueAt(edge1.FirstParameter))
64 | p12 = np.array(edge1.valueAt(edge1.LastParameter))
65 | p21 = np.array(edge2.valueAt(edge2.FirstParameter))
66 | p22 = np.array(edge2.valueAt(edge2.LastParameter))
67 | if normal is None:
68 | n = None
69 | else:
70 | n = np.array(normal.tangentAt(normal.FirstParameter))
71 | l = [np.linalg.norm(i) for i in [p11 - p21, p11 - p22, p12 - p21, p12 - p22]]
72 | l_min = min(l)
73 | i = l.index(l_min)
74 | if i == 0:
75 | t1 = edge1.FirstParameter
76 | s1 = 1
77 | t2 = edge2.FirstParameter
78 | s2 = 1
79 |
80 | if i == 1:
81 | t1 = edge1.FirstParameter
82 | s1 = 1
83 | t2 = edge2.LastParameter
84 | s2 = -1
85 |
86 | if i == 2:
87 | t1 = edge1.LastParameter
88 | s1 = -1
89 | t2 = edge2.FirstParameter
90 | s2 = 1
91 |
92 | if i == 3:
93 | t1 = edge1.LastParameter
94 | s1 = -1
95 | t2 = edge2.LastParameter
96 | s2 = -1
97 | if edge1.Orientation != 'Forward':
98 | s1 *= -1
99 | if edge2.Orientation != 'Forward':
100 | s2 *= -1
101 |
102 | p1 = np.array(edge1.valueAt(t1))
103 | p2 = np.array(edge2.valueAt(t2))
104 | tang1 = np.array(edge1.tangentAt(t1))
105 | tang2 = np.array(edge2.tangentAt(t2))
106 | bs = curve_between_lines(p1, p2, s1 * tang1, s2 * tang2, n)
107 | Part.show(bs.toShape())
108 |
109 | def least_square_normal(points):
110 | mat = np.array(p.tolist + [[1., 1., 1.]])
111 | rhs = [0] * len(points) + [1] # add a condition that the sum of all n_i is not equal to zero
112 | normal = np.linalg.lstsq(mat, rhs)
113 | normal /= np.linalg.norm(normal)
114 | return normal
115 |
116 | import numpy as np
117 | import FreeCADGui as gui
118 | from scipy.optimize import minimize
119 |
120 | def least_square_circle():
121 | """
122 | finds the center and the radius (approximatly) of an edge
123 | """
124 | edge = gui.Selection.getSelectionEx()[0].SubObjects[0]
125 | points = np.array(edge.discretize(100))
126 | def min_function(center):
127 | """
128 | minimizing the std deviation of the distance to the center
129 | """
130 | dx = points - center
131 | ri = np.linalg.norm(dx, axis=1)
132 | return np.std(ri)
133 | start = np.mean(points, axis=0)
134 | center = minimize(min_function, start).x
135 | dx = points - center
136 | ri = np.linalg.norm(dx, axis=1)
137 | r_mean = np.mean(ri)
138 | r_std = np.std(ri)
139 | print("center = {} \nradius = {} \nstd = {}".format(center, r_mean, r_std))
140 |
141 | def least_square_point():
142 | import FreeCADGui as Gui
143 | from freecad import app
144 | lines = Gui.Selection.getSelection()
145 | one = np.eye(3)
146 | mat = []
147 | rhs = []
148 | for line in lines:
149 | start = np.array([[*line.Start]]).T
150 | end = np.array([[*line.End]]).T
151 | t = end - start
152 | t /= np.linalg.norm(t)
153 | A = t @ t.T
154 | t = t[0]
155 | r = start - A @ start
156 | m = one - A
157 | for i in [0, 1, 2]:
158 | mat.append(m[i])
159 | rhs.append(r[i])
160 | mat = np.array(mat)
161 | rhs = np.array(rhs)
162 | sol = np.linalg.lstsq(mat, rhs, rcond=None)
163 | Part.show(Part.Point(app.Vector(*sol[0])).toShape())
164 |
165 | def proj_point2line():
166 | import FreeCADGui as Gui
167 | from freecad import app
168 | point, line = Gui.Selection.getSelection()
169 | point = point.Shape.Vertexes[0]
170 | point = np.array([point.X, point.Y, point.Z])
171 | start = np.array([*line.Start])
172 | end = np.array([*line.End])
173 | t = end - start
174 | t /= np.linalg.norm(t)
175 | diff = point - start
176 | s = diff @ t
177 | sol = start + s * t
178 | Part.show(Part.Point(app.Vector(*sol)).toShape())
179 |
180 |
181 |
182 | def apply_transformation():
183 | obj = gui.Selection.getSelection()[0]
184 | obj.Shape = obj.Shape.transformGeometry(obj.Placement.Matrix)
185 | obj.Placement = App.Matrix()
--------------------------------------------------------------------------------
/freecad/frametools/make_beam.ui:
--------------------------------------------------------------------------------
1 |
2 |
3 | FrameAndBeamGui::CreateBeamParameters
4 |
5 |
6 |
7 | 0
8 | 0
9 | 271
10 | 604
11 |
12 |
13 |
14 | Create beam
15 |
16 |
17 | -
18 |
19 |
-
20 |
21 |
22 | Profile
23 |
24 |
25 | true
26 |
27 |
28 |
29 | -
30 |
31 |
32 | true
33 |
34 |
35 |
36 | -
37 |
38 |
39 | Path
40 |
41 |
42 | true
43 |
44 |
45 |
46 | -
47 |
48 |
49 | true
50 |
51 |
52 |
53 | -
54 |
55 |
56 |
57 | 0
58 | 0
59 |
60 |
61 |
62 | Extent 1:
63 |
64 |
65 |
66 | -
67 |
68 |
69 |
70 | 0
71 | 0
72 |
73 |
74 |
75 |
76 | 0
77 | 5
78 |
79 |
80 |
81 | mm
82 |
83 |
84 | 0.000000000000000
85 |
86 |
87 |
88 | -
89 |
90 |
91 |
92 | 0
93 | 0
94 |
95 |
96 |
97 | Extent 2:
98 |
99 |
100 |
101 | -
102 |
103 |
104 |
105 | 0
106 | 0
107 |
108 |
109 |
110 |
111 | 0
112 | 5
113 |
114 |
115 |
116 | mm
117 |
118 |
119 | 0.000000000000000
120 |
121 |
122 |
123 | -
124 |
125 |
126 |
127 | 0
128 | 0
129 |
130 |
131 |
132 | Profile rotation:
133 |
134 |
135 |
136 | -
137 |
138 |
139 |
140 | 0
141 | 0
142 |
143 |
144 |
145 |
146 | 0
147 | 5
148 |
149 |
150 |
151 | deg
152 |
153 |
154 | 0.000000000000000
155 |
156 |
157 |
158 | -
159 |
160 |
161 | Create
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # FreeCAD Frame Workbench
2 | A frame-module for FreeCAD
3 |
4 | ## Requirements
5 | * FreeCAD >= v0.16
6 | * scipy v?? <----
7 |
8 | ## Installation
9 | ### Automatic Installation
10 | As of v0.17 it's possible to use the built-in FreeCAD [Addon Manager](https://github.com/FreeCAD/FreeCAD-addons#1-builtin-addon-manager)
11 | to install this workbench.
12 |
13 | ### Manual Installation
14 | * `cd ~/.freecad/Mod || mkdir ~/.freecad/Mod && cd ~/.freecad/Mod`
15 | * `git clone https://github.com/looooo/freecad_frame.git`
16 | * restart FreeCAD
17 |
18 | ## Usage
19 |
20 | ### Create a Beam
21 |
22 | * Create new document
23 | * Create profile. Possible sources for profile:
24 | * Sketch
25 | * Draw a sketch.
26 | * Go to Part WB.
27 | * Select profile.
28 | * Create Face from profile.
29 | * "Profile" command in Arch WB allows to insert some predefined
30 | profiles like RHS.
31 |
32 | The profile should be in XY-plane (this is current limitation).
33 | * Create a path to make beam. The path can be:
34 | * Straight lines from any sketch.
35 | * Straight lines from Draw WB.
36 |
37 | Curves can't be used as path for beams.
38 | * Go to Frame and beams WB.
39 | * Select "Beam" command
40 | * Select profile.
41 | * Select path.
42 | * Enter extents if beams should be longer than path.
43 | * Enter rotation for profile to turn beam around its path.
44 | * Press "Create".
45 | * Select another path or profile to create more beams or press
46 | "Close" to finish command.
47 |
48 | ### Miter cut two beams
49 |
50 | * Select "Miter Cut" command from Frame and beam WB
51 | * Select first beam.
52 | * Select second beam which intesects the first beam.
53 | * Press "Cut".
54 | * To cut another beams select them and press "Cut" or press "Close" to
55 | finish command.
56 |
57 | If one or two beams are selected when "Miter Cut" command activated
58 | they will be preselected as "Beam 1" and "Beam 2". To cut them press
59 | "Cut" button.
60 |
61 | ### Plane Cut
62 |
63 | * Select "Plane Cut" command from Frame and beam WB
64 | * Select first beam.
65 | * Select face of another beam which intersects the first beam.
66 | * Press "Cut".
67 | * To cut another beams select them and press "Cut" or press "Close" to
68 | finish command.
69 |
70 | If beam and beam face (in that order) are selected when "Plane Cut"
71 | command activated they will be preselected as "Beam to cut" and "Cut
72 | with". To cut them press "Cut" button.
73 |
74 | ### Shape Cut
75 |
76 | * Select "Shape Cut" command from Frame and beam WB
77 | * Select first beam.
78 | * Select second beam which intesects the first beam.
79 | * Press "Cut".
80 | * To cut another beams select them and press "Cut" or press "Close" to
81 | finish command.
82 |
83 | If one or two beams are selected when "Shape Cut" command activated
84 | they will be preselected as "Beam 1" and "Beam 2". To cut them press
85 | "Cut" button.
86 |
87 | ## Example
88 |
89 | Gate leaf creation.
90 |
91 |
92 |
93 | Prerequisites:
94 |
95 | * Made from hollow profile with rounded corners with size 40x20mm and thickness 2mm.
96 | * Internal height between top and bottom bars is 1500mm.
97 | * Horizontal size 1700mm.
98 | * Middle bars added to have decorations inside between top and middle
99 | bar and bottom and middle bar (not shown due to lack of
100 | object). Height of decorations is 155mm.
101 | * Diagonal supports added.
102 |
103 | 1. Create profile.
104 |
105 |
106 |
107 | 1. Create a new sketch in XY plane.
108 | 2. Repeat drawing from picture above. Outer rounding radius is 3mm
109 | and inner rounding radius is 2mm.
110 | 3. Close sketch.
111 | 4. Select Part Workbench and convert sketch to face ("Make face from
112 | wires" command).
113 |
114 | 2. Create sketch for beam axes. Since this is gate leaf the sketch
115 | should be created, for example, in XZ plane.
116 |
117 |
118 |
119 | This will require a little bit more work because some measurements
120 | give outer sizes, some give inner and no one give axes. The
121 | diagonal supports are drawn according to other beams. To achieve
122 | correct lines construction geometry should be used for everything
123 | that is not beam in final design like outer edge of gate.
124 |
125 | 3. Create outer beams. Since they should fully intersect the "Extent
126 | 1" and "Extent 2" should have value 20mm at least. Please don't
127 | enter very big values because some extra parts can be left after
128 | cutting.
129 |
130 |
131 |
132 | 4. Create inner beams. They axes long enough to get full intersection
133 | with other beams so the extents can be left as is.
134 |
135 |
136 |
137 | 5. Miter cut outer beams.
138 |
139 |
140 |
141 | Please note that *every* cut operation creates a new beam so if
142 | beam needs to be cut twice it should be reselected after the
143 | first cut. Removing cut beam can break other cut beams if it is
144 | used in the other beam cut operation.
145 |
146 | 6. Plane cut inner beams by outer beams inner planes. Plane cut
147 | diagonals.
148 |
149 |
150 |
151 | 7. Select TechDraw Workbench
152 |
153 |
154 |
155 | 1. Insert page from template and select A4 Portrait template.
156 | 2. Go to the 3D view and select "Front View" to see front side. The
157 | TechDraw inserts view in current active projection.
158 | 3. Select Main Diagonal.
159 | 4. Go to the inserted page and press "Insert view". The page will
160 | contain huge page placed as viewed.
161 | 5. Select view in tree and set scale to 0.1. This makes beam
162 | completely inside drawing.
163 | 6. The TechDraw don't allow to simple rotations like "make this
164 | line vertical" so view should be rotated by -55.582°
165 | (measured as angle between vertical border and angled one). This
166 | make beam straight vertical on drawing.
167 | 7. Add dimensions to overall length and how to cut corners.
168 |
169 | Repeat steps above for every unique beam. Fill fields below as
170 | needed. This gives the set of drawing suitable to create all parts
171 | of gate leaf.
172 |
173 | To have drawing with guide to assemble it full view (with all part
174 | selected) should be inserted on a new page. Welding annotations
175 | will describe how to join parts.
176 |
177 | ## Feedback
178 | Offer feedback and discuss this workbench in the [dedicated FreeCAD forum thread]()
179 |
180 | ## Known Limitations
181 |
182 | ## Developer
183 | [@looooo](https://github.com/looooo)
184 |
185 | ## License
186 | GNU Lesser General Public License v2.1
187 |
--------------------------------------------------------------------------------
/freecad/frametools/fem2d.py:
--------------------------------------------------------------------------------
1 | from PySide import QtGui, QtCore
2 | import FreeCADGui as Gui
3 | import FreeCAD as App
4 | import numpy as np
5 | from scipy import sparse
6 | from scipy.sparse import linalg
7 | from freecad.frametools import ICON_PATH
8 | import os
9 | import yaml
10 |
11 | class PropertyEditor(object):
12 | widget_name = 'properties editor'
13 |
14 | def __init__(self, obj):
15 | self.obj = obj
16 | self.form = []
17 | self.base_widget = QtGui.QWidget()
18 | self.form.append(self.base_widget)
19 | self.base_widget.setWindowTitle(self.widget_name)
20 | self.base_widget.setLayout(QtGui.QVBoxLayout())
21 | self.plain_text = QtGui.QPlainTextEdit()
22 | self.plain_text.setTabStopWidth(30)
23 | self.plain_text.document().setPlainText(yaml.dump(self.obj.parameter_dict))
24 | self.addRefButton = QtGui.QPushButton("add entities")
25 | self.addRefButton.clicked.connect(self.add_entities)
26 | self.base_widget.layout().addWidget(self.addRefButton)
27 | self.base_widget.layout().addWidget(self.plain_text)
28 |
29 | def accept(self):
30 | try:
31 | self.obj.parameter_dict = yaml.load(self.plain_text.document().toPlainText())
32 | except Exception as e:
33 | print(e)
34 | return
35 | else:
36 | Gui.Control.closeDialog()
37 |
38 | def reject(self):
39 | Gui.Control.closeDialog()
40 |
41 | def add_entities(self):
42 | sel = Gui.Selection.getSelectionEx()[0]
43 | for i, name in enumerate(sel.SubElementNames):
44 | if i != 0:
45 | self.plain_text.insertPlainText(", ")
46 | self.plain_text.insertPlainText(name)
47 |
48 | class ViewProviderGenericSolver(object):
49 | def __init__(self, obj):
50 | obj.Proxy = self
51 | self.obj = obj.Object
52 |
53 | def setupContextMenu(self, obj, menu):
54 | action = menu.addAction("edit parameters")
55 | action.triggered.connect(self.edit_dict)
56 | action = menu.addAction("solve")
57 | action.triggered.connect(self.solve)
58 | action = menu.addAction("show_result")
59 | action.triggered.connect(self.show_result)
60 | action = menu.addAction("edge_min_value")
61 | action.triggered.connect(self.edge_min_value)
62 |
63 | def edit_dict(self):
64 | Gui.Control.showDialog(PropertyEditor(self.obj))
65 |
66 | def solve(self):
67 | self.obj.Proxy.solve()
68 |
69 | def getIcon(self):
70 | return os.path.join(ICON_PATH, "generic_solver.svg")
71 |
72 | def show_result(self):
73 | tris = []
74 | for domain in self.obj.Proxy.lambda_domains:
75 | tris += np.array(self.obj.Proxy.n2elements[domain]).tolist()
76 |
77 | from freecad.plot import Plot
78 | from matplotlib import pyplot as plt
79 | import matplotlib.cm as cm
80 | fig = Plot.figure()
81 | fig.fig.add_axes()
82 | ax = fig.fig.axes[0]
83 | ax.tricontourf(self.obj.Proxy.nodes[:, 0],
84 | self.obj.Proxy.nodes[:, 1],
85 | tris, self.obj.Proxy.t, levels=100, cmap=cm.coolwarm)
86 | ax.grid()
87 | ax.set_aspect('equal')
88 | # fig.fig.colorbar()
89 | fig.show()
90 |
91 | def edge_min_value(self):
92 | message_box = QtGui.QMessageBox()
93 | message_box.setText(self.obj.Proxy.edge_min_value())
94 | message_box.exec_()
95 |
96 | def attach(self, view_obj):
97 | self.view_obj = view_obj
98 | self.obj = view_obj.Object
99 |
100 | def __getstate__(self):
101 | pass
102 |
103 | def __setstate__(self, state):
104 | pass
105 |
106 | class GenericSolver():
107 | def __init__(self, obj, geo_obj, mesh_obj):
108 | '''A generic solver class for FEM-analysis'''
109 | obj.Proxy = self
110 | obj.addProperty("App::PropertyLink","geo_obj","Link","A shape object").geo_obj = geo_obj
111 | obj.addProperty("App::PropertyLink","mesh_obj","Link","A mesh object").mesh_obj = mesh_obj
112 | obj.addProperty('App::PropertyPythonObject', 'parameter_dict', 'parameters', 'parameters as dict')
113 | self.obj = obj
114 | self.init_parameters()
115 |
116 | @property
117 | def n2e(self):
118 | return self.obj.parameter_dict["entities"]
119 |
120 | @property
121 | def n2tnv(self):
122 | return self.obj.parameter_dict["properties"]
123 |
124 | def init_parameters(self):
125 | self.obj.parameter_dict = {"entities": {"name": ["entity1", "entity2"]},
126 | "properties": {"name": {"type1": "value1", "type2": "value2"}}}
127 |
128 | def solve(self):
129 | self.lambda_domains = [name for name, prop in self.n2tnv.items() if "lambda" in prop]
130 | dirichlet_domains = [name for name, prop in self.n2tnv.items() if ("T" in prop and not "R" in prop)]
131 | neumann_domains = [name for name, prop in self.n2tnv.items() if "q" in prop]
132 | mixed_domains = [name for name, prop in self.n2tnv.items() if ("T" in prop and "R" in prop)]
133 | self.n2elements = {}
134 | for name, entities in self.n2e.items():
135 | elements = []
136 | for entity in entities:
137 | if "Face" in entity:
138 | elements += (self.obj.mesh_obj.FemMesh.getFacesByFace(
139 | self.obj.geo_obj.Shape.getElement(entity)))
140 | if "Edge" in entity:
141 | elements += (self.obj.mesh_obj.FemMesh.getEdgesByEdge(
142 | self.obj.geo_obj.Shape.getElement(entity)))
143 | self.n2elements[name] = [np.array(self.obj.mesh_obj.FemMesh.getElementNodes(el_index), dtype=int) - 1 for el_index in elements]
144 | self.nodes = np.array(list(map(list, self.obj.mesh_obj.FemMesh.Nodes.values())))
145 | self.nodes = self.nodes[:,:-1] # delete z-values
146 |
147 | ## creating the matrix of the diffusion-matrix
148 | mat_entries = []
149 | for name in self.lambda_domains:
150 | lam = self.n2tnv[name]["lambda"]
151 | for element in self.n2elements[name]:
152 | mat_entries += self.diffuseTerm(element, lam)
153 |
154 | neumann_entries = []
155 | for name in neumann_domains:
156 | q = self.n2tnv[name]["q"]
157 | for element in self.n2elements[name]:
158 | neumann_entries += self.neumannTerm(element, q)
159 |
160 | for name in mixed_domains:
161 | T = self.n2tnv[name]["T"]
162 | R = self.n2tnv[name]["R"]
163 | for element in self.n2elements[name]:
164 | neumann_entries += self.neumannTerm(element, T / R)
165 | mat_entries += self.unknown_neumannTerm(element, 1. / R)
166 |
167 | dirichlet_indices = []
168 | dirichlet_data = []
169 | for name in dirichlet_domains:
170 | T = self.n2tnv[name]["T"]
171 | nodes = []
172 | for elements in self.n2elements[name]:
173 | nodes += elements.tolist()
174 | nodes = list(set(nodes)) # removing duplicated nodes
175 | dirichlet_indices += nodes
176 | dirichlet_data += [T] * len(nodes)
177 |
178 | # assemblying:
179 | row_indices, col_indices, values = np.array(mat_entries).T
180 | row_indices = np.int64(row_indices)
181 | col_indices = np.int64(col_indices)
182 | mat = sparse.csr_matrix((values, (row_indices, col_indices)))
183 | rhs = sparse.csr_matrix((len(self.nodes), 1))
184 |
185 | # now we have to delete all matrix entries which are on the dirichlet boundary
186 | # a diagonal matrix to delete all the entries on bc_d data
187 |
188 | if dirichlet_data and dirichlet_indices:
189 | d_mod_indices = list(set(dirichlet_indices))
190 | bc_d_mat_1 = sparse.csr_matrix(([1] * len(d_mod_indices), (d_mod_indices, d_mod_indices)),
191 | shape=(len(self.nodes), len(self.nodes)))
192 | bc_d_mat_2 = sparse.csr_matrix(([1] * len(dirichlet_indices), (dirichlet_indices, dirichlet_indices)),
193 | shape=(len(self.nodes), len(self.nodes)))
194 | dia_mat = sparse.dia_matrix(([1] * len(self.nodes), 0),
195 | shape=(len(self.nodes), len(self.nodes)))
196 | mat = (dia_mat - bc_d_mat_1) @ mat + bc_d_mat_2
197 |
198 | rhs += sparse.csr_matrix((dirichlet_data, (dirichlet_indices, [0] * len(dirichlet_indices))),
199 | shape=(len(self.nodes), 1))
200 | if neumann_entries:
201 | neumann_indices, neumann_data = np.array(neumann_entries).T
202 | neumann_indices = np.int64(neumann_indices)
203 | rhs += sparse.csr_matrix((neumann_data, (neumann_indices, [0] * len(neumann_indices))),
204 | shape=(len(self.nodes), 1))
205 | self.t = linalg.spsolve(mat, rhs)
206 |
207 | def diffuseTerm(self, element, lam):
208 | BTB = self.get_BTB(element)
209 | values = lam * BTB
210 | row_indices = self.get_row_indices(element)
211 | col_indices = row_indices.T
212 | return np.array([row_indices.flatten(), col_indices.flatten(), values.flatten()]).T.tolist()
213 |
214 | def neumannTerm(self, element, q):
215 | HTH = self.get_HTH(element)
216 | values = HTH @ np.array([[q], [q]]).flatten()
217 | return np.array([element, values]).T.tolist()
218 |
219 | def unknown_neumannTerm(self, element, q):
220 | HTH = self.get_HTH(element) * q
221 | row_indices = self.get_row_indices(element)
222 | col_indices = row_indices.T
223 | return np.array([row_indices.flatten(), col_indices.flatten(), HTH.flatten()]).T.tolist()
224 |
225 | def get_HTH(self, element):
226 | x1, x2 = self.nodes[element]
227 | l = np.linalg.norm(x2 - x1)
228 | return l * np.array([
229 | [1. / 3., 1. / 6.],
230 | [1. / 6., 1. / 3.]], dtype=float)
231 |
232 | def get_BTB(self, element):
233 | x1, y1, x2, y2, x3, y3 = self.nodes[element].flatten()
234 | y12 = y2 - y1; y23 = y3 - y2; y31 = y1 - y3
235 | x12 = x2 - x1; x23 = x3 - x2; x31 = x1 - x3
236 | area = 0.5 * abs(x12 * y31 - x31 * y12)
237 | B = np.array([[-y23, -y31, -y12],
238 | [ x23, x31, x12]]) / (2 * area)
239 | return area * B.T @ B
240 |
241 | def get_row_indices(self, element):
242 | return np.array([element] * len(element))
243 |
244 | def onDocumentRestored(self, obj):
245 | self.obj = obj
246 |
247 | def __getstate__(self):
248 | return None
249 |
250 | def __setstate__(self, state):
251 | return None
252 |
253 | def edge_min_value(self):
254 | output = ""
255 | for name, entities in self.n2e.items():
256 | nodes = []
257 | for entity in entities:
258 | if "Edge" in entity:
259 | nodes += list(self.obj.mesh_obj.FemMesh.getNodesByEdge(
260 | self.obj.geo_obj.Shape.getElement(entity)))
261 | nodes = list(set(nodes))
262 | if nodes:
263 | output += "min value of " + name + "= {0:.3f} °C".format(min(self.t[nodes]))
264 | output += "\n"
265 | return output
266 |
267 | def make_GenericSolver(geo_obj, mesh_obj):
268 | obj = App.ActiveDocument.addObject("App::FeaturePython", "generic_solver")
269 | GenericSolver(obj, geo_obj, mesh_obj)
270 | ViewProviderGenericSolver(obj.ViewObject)
271 | App.ActiveDocument.recompute()
272 | return obj
273 |
--------------------------------------------------------------------------------
/freecad/frametools/icons/nurbs_connect.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
358 |
--------------------------------------------------------------------------------
/freecad/frametools/icons/beam.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
245 |
--------------------------------------------------------------------------------
/freecad/frametools/beamobj.py:
--------------------------------------------------------------------------------
1 | from __future__ import division
2 | import copy
3 |
4 | import os
5 | import numpy as np
6 |
7 | import FreeCAD as App
8 | import Part
9 |
10 | from freecad.frametools import ICON_PATH
11 |
12 | __all__ = [
13 | "Beam",
14 | "CBeam",
15 | "ViewProviderBeam",
16 | "ViewProviderCBeam"]
17 |
18 |
19 | class Beam():
20 | def __init__(self, obj, profile, path, path_name):
21 | '''Beam: representing a straight extrusion of a profile'''
22 | print("was soll das")
23 | obj.addProperty("App::PropertyString", "type", "Beam", "type of the object").type = "beam"
24 | obj.addProperty("App::PropertyLink","profile","Beam","sketch profile of the beam").profile = profile
25 | obj.addProperty("App::PropertyLink","path","Beam","path of the beam").path = path
26 | obj.addProperty("App::PropertyString","path_name","Beam", "name of beam line").path_name = path_name
27 | obj.addProperty("App::PropertyDistance", "exdent_1", "Beam", "exdent side 1").exdent_1 = "0 mm"
28 | obj.addProperty("App::PropertyDistance", "exdent_2", "Beam", "exdent side 2").exdent_2 = "0 mm"
29 | obj.addProperty("App::PropertyAngle", "Rotation", "Beam", "rotation of the profile").Rotation = 0
30 | obj.setEditorMode("path_name", 1)
31 | obj.Proxy = self
32 | self.Object = obj
33 |
34 | def initialize(self):
35 | print("initialize ...")
36 |
37 | @property
38 | def profile(self):
39 | if isinstance(self.Object.profile.Shape, Part.Face):
40 | # create a copy of the face
41 | return Part.Face(self.Object.profile.Shape)
42 | wires_copy = wires = copy.copy(self.Object.profile.Shape.Wires)
43 | # check boundingbox diagonals to get outer shape
44 | if len(wires) > 1:
45 | diagonals = [wire.BoundBox.DiagonalLength for wire in wires]
46 | pos = diagonals.index(max(diagonals))
47 | # reaordering
48 | external_wire = wires[pos]
49 | wires.pop(pos)
50 | wires.insert(0, external_wire)
51 | # check face oriendation
52 | orientation = wires[0].Orientation
53 | normal = Part.Face(wires[0]).normalAt(0, 0)
54 | wires_copy = [wires[0]]
55 | for wire in wires[1:]:
56 | fi = Part.Face(wire)
57 | if fi.normalAt(0, 0).dot(normal) > 0:
58 | fi.reverse()
59 | wires_copy.append(fi.Wires[0])
60 | face = Part.Face(wires_copy)
61 | return face
62 |
63 | @property
64 | def outer_shape(self):
65 | path, a, b, n = self.path_a_b_n
66 | profile = Part.Face(self.profile.Wires[0])
67 | new_profile = self.transform_to_n(profile, a - n * self.Object.exdent_1.Value, n)
68 | return new_profile.extrude(App.Vector(b - a) + n * (self.Object.exdent_1.Value + self.Object.exdent_2.Value))
69 |
70 | def attach(self, fp):
71 | print("hi")
72 | fp.Proxy.Object = fp
73 |
74 | def execute(self, fp):
75 | '''Do something when doing a recomputation, this method is mandatory'''
76 | fp.Proxy.Object = fp
77 | path, a, b, n = self.path_a_b_n
78 | new_profile = self.transform_to_n(self.profile, a - n * fp.exdent_1.Value, n)
79 | fp.Shape = new_profile.extrude(App.Vector(b - a) + n * (fp.exdent_1.Value + fp.exdent_2.Value))
80 |
81 | def transform_to_n(self, profile, p, n):
82 | '''transform a profile (Shape):
83 | rotating from t to n
84 | and translating from 0 to p'''
85 |
86 |
87 | n.normalize()
88 | t = sketch_normal(n, self.Object.path)
89 | t.normalize()
90 | v = n.cross(t)
91 | v.normalize()
92 |
93 | rot_mat = App.Matrix()
94 | rot_mat.A11 = t.x
95 | rot_mat.A21 = t.y
96 | rot_mat.A31 = t.z
97 | rot_mat.A12 = v.x
98 | rot_mat.A22 = v.y
99 | rot_mat.A32 = v.z
100 | rot_mat.A13 = n.x
101 | rot_mat.A23 = n.y
102 | rot_mat.A33 = n.z
103 | rot_mat.A14 = p.x
104 | rot_mat.A24 = p.y
105 | rot_mat.A34 = p.z
106 | if hasattr(self.Object.profile, "Sources"):
107 | translation = self.Object.profile.Sources[0].Placement.Base
108 | #translation += self.Object.profile.Placement.Base
109 | else:
110 | translation = self.Object.profile.Placement.Base
111 | print(self.Object.profile.Placement)
112 | print(profile.Placement)
113 | print(profile.Placement)
114 | rot_mat_2 = profile.Placement.toMatrix().inverse()
115 | profile.Placement.Base -= translation
116 | rot_mat_1 = App.Matrix()
117 | rot_mat_1.rotateZ(np.deg2rad(self.Object.Rotation.Value))
118 |
119 | placement = rot_mat.multiply(rot_mat_1.multiply(rot_mat_2))
120 | profile.Placement = App.Placement(placement).multiply(profile.Placement)
121 |
122 | return profile
123 |
124 | # @property
125 | # def midpoint(self):
126 | # profile_midpoint = filter(lambda x: isinstance(x, App.Base.Vector), self.Object.profile.Geometry)
127 | # if len(profile_midpoint) == 0 or profile_midpoint is None:
128 | # profile_midpoint = App.Vector(0, 0, 0)
129 | # else:
130 | # profile_midpoint = profile_midpoint[0]
131 | # return profile_midpoint
132 |
133 | @property
134 | def path_a_b_n(self):
135 | path = self.Object.path.Shape.getElement(self.Object.path_name)
136 | a, b = path.Vertexes
137 | n = b.Point - a.Point
138 | n.normalize()
139 | return (path, a.Point, b.Point, n)
140 |
141 | @property
142 | def profile_bound_box(self):
143 | fac = 0.3
144 | bb = self.Object.profile.Shape.BoundBox
145 | xmax = bb.XMax + bb.XLength * fac
146 | xmin = bb.XMin - bb.XLength * fac
147 | ymax = bb.YMax + bb.YLength * fac
148 | ymin = bb.YMin - bb.YLength * fac
149 | pol = Part.makePolygon(
150 | [App.Vector(xmax, ymax, 0),
151 | App.Vector(xmax, ymin, 0),
152 | App.Vector(xmin, ymin, 0),
153 | App.Vector(xmin, ymax, 0),
154 | App.Vector(xmax, ymax, 0)])
155 | path, a, b, n = self.path_a_b_n
156 | return self.transform_to_n(pol, a, n)
157 |
158 | def project_profile_bound_box(self, plane_n, plane_p):
159 | bb = self.profile_bound_box.Vertexes
160 | vectors = [v.Point for v in bb]
161 | path, a, b, n = self.path_a_b_n
162 | projected = [project_to_plane(plane_p, plane_n, v, n) for v in vectors]
163 | projected.append(projected[0])
164 | pol = Part.makePolygon(projected)
165 | return pol
166 |
167 | def __getstate__(self):
168 | return None
169 |
170 | def __setstate__(self, state):
171 | return None
172 |
173 | def attach(self):
174 | pass
175 |
176 |
177 | class ViewProviderBeam:
178 | def __init__(self, obj):
179 | ''' Set this object to the proxy object of the actual view provider '''
180 | obj.Proxy = self
181 |
182 | def attach(self, vobj):
183 | self.vobj = vobj
184 |
185 | def getIcon(self):
186 | _dir = os.path.dirname(os.path.realpath(__file__))
187 | return(_dir + "/" + "icons/beam.svg")
188 |
189 | def __getstate__(self):
190 | return None
191 |
192 | def __setstate__(self, state):
193 | return None
194 |
195 |
196 | class CBeam(object):
197 | def __init__(self, obj, beam):
198 | "'''Beam: representing a straight extrusion of a profile'''"
199 | obj.addProperty("App::PropertyString", "type", "Beam", "type of the object").type = "c_beam"
200 | obj.addProperty("App::PropertyLink","this_beam","Beam","sketch profile of the beam").this_beam = beam
201 | obj.addProperty("App::PropertyLink","cut_obj","Cut","link to cut obj")
202 | obj.addProperty("App::PropertyString","cut_type","Cut","type of cut obj")
203 | obj.addProperty("App::PropertyString","cut_obj_name","Cut","type of cut obj")
204 | obj.addProperty("App::PropertyBool", "cut", "Beam", "show the cut").cut = True
205 | self.Object = obj
206 | obj.Proxy = self
207 |
208 | def attach(self, fp):
209 | self.Object = fp
210 |
211 | def execute(self, fp):
212 | fp.Proxy.Object = fp
213 | if self.Object.cut:
214 | if fp.cut_type == "miter":
215 | fp.Shape = self.miter_cut(self.Object.cut_obj)
216 | elif fp.cut_type == "cut":
217 | face = self.Object.cut_obj.Shape.getElement(self.Object.cut_obj_name)
218 | fp.Shape = self.face_cut(self.Object.cut_obj, face)
219 | elif fp.cut_type == "shape_cut":
220 | fp.Shape = self.shape_cut(self.Object.cut_obj)
221 | else:
222 | fp.Shape = fp.this_beam.Shape
223 |
224 | def find_top_beam(self, beam):
225 | if "path_a_b_n" in dir(beam.Proxy):
226 | beam.Proxy.Object = beam
227 | return beam
228 | else:
229 | return self.find_top_beam(beam.this_beam)
230 |
231 | # def find_path_a_b_n(self, beam):
232 | # if not hasattr(beam.Proxy, "path_a_b_n"):
233 | # return beam.this_beam.Proxy.path_a_b_n
234 | # else:
235 | # return beam.Proxy.path_a_b_n
236 |
237 | # def find_project_profile_bound_box(self, beam):
238 | # if not hasattr(beam.Proxy, "project_profile_bound_box"):
239 | # return beam.this_beam.Proxy.project_profile_bound_box
240 | # else:
241 | # return beam.Proxy.project_profile_bound_box
242 |
243 | def miter_cut(self, beam):
244 | # get nearest point of the two lines
245 | top_beam_1 = self.find_top_beam(self.Object.this_beam)
246 | top_beam_2 = self.find_top_beam(beam)
247 |
248 | path, p_1_a, p_1_b, n1 = top_beam_1.Proxy.path_a_b_n
249 | path, p_2_a, p_2_b, n2 = top_beam_2.Proxy.path_a_b_n
250 | arr = [[p_1_a, p_2_a],
251 | [p_1_a, p_2_b],
252 | [p_1_b, p_2_a],
253 | [p_1_b, p_2_b]]
254 |
255 | l_arr = list(map(norm, arr))
256 | min_val = min(l_arr)
257 | min_item = l_arr.index(min_val)
258 |
259 | if min_item in [0, 1]:
260 | n1 *= -1
261 | if min_item in [0, 2]:
262 | n2 *= -1
263 |
264 | bp1, bp2 = arr[min_item]
265 |
266 | # calculating the intersection-point p and face-normal n
267 | t = n1.cross(n2)
268 | np_n1, np_n2, np_t, np_bp1, np_bp2 = list(map(to_np, [n1, n2, t, bp1, bp2]))
269 | np_mat = np.array([np_n1, -np_n2, t])
270 | np_mat = np_mat.transpose()
271 | np_rhs = np_bp2 - np_bp1
272 | l1, l2, l3 = np.linalg.solve(np_mat, np_rhs)
273 | n = n1 - n2
274 | n.normalize()
275 | p = bp1 + n1 * l1 + t * l3 * 0.5
276 |
277 | pol = top_beam_1.Proxy.project_profile_bound_box(n, p)
278 | face = Part.Face(pol.Wires)
279 | solid = face.extrude(n1 * 100)
280 | return self.Object.this_beam.Shape.cut(solid)
281 |
282 | def face_cut(self, beam, face):
283 | top_beam_1 = self.find_top_beam(self.Object.this_beam)
284 | top_beam_2 = self.find_top_beam(beam)
285 | path, p_1_a, p_1_b, n1 = top_beam_1.Proxy.path_a_b_n
286 | path, p_2_a, p_2_b, n2 = top_beam_2.Proxy.path_a_b_n
287 | arr = [p_1_a, p_1_b]
288 | n = face.normalAt(0, 0)
289 | n.normalize()
290 |
291 | n_dist = lambda p: abs((p - p_2_a).dot(n))
292 | l_arr = list(map(n_dist, arr))
293 | min_val = min(l_arr)
294 | min_item = l_arr.index(min_val)
295 |
296 | if min_item in [0]:
297 | n1 *= -1
298 |
299 | # get the face normal and center point
300 |
301 | p = face.CenterOfMass
302 | pol = top_beam_1.Proxy.project_profile_bound_box(n, p)
303 | face = Part.Face(pol.Wires)
304 | solid = face.extrude(n1 * 100)
305 | return self.Object.this_beam.Shape.cut(solid)
306 |
307 | def shape_cut(self, beam):
308 | top_beam = self.find_top_beam(beam)
309 | return self.Object.this_beam.Shape.cut(top_beam.Proxy.outer_shape)
310 |
311 | def __getstate__(self):
312 | return None
313 |
314 | def __setstate__(self, state):
315 | return None
316 |
317 |
318 | class ViewProviderCBeam(ViewProviderBeam):
319 | def __init__(self, obj):
320 | ''' Set this object to the proxy object of the actual view provider '''
321 | obj.Proxy = self
322 | self.vobj = obj
323 |
324 | def claimChildren(self):
325 | self.vobj.Object.this_beam.ViewObject.Visibility=False
326 | return [self.vobj.Object.this_beam]
327 |
328 | def getIcon(self):
329 | _dir = os.path.dirname(os.path.realpath(__file__))
330 | return(_dir + "/" + "icons/beam.svg")
331 |
332 |
333 | def norm(p):
334 | return (p[0] - p[1]).Length
335 |
336 |
337 | def sketch_normal(n, normal_info=None):
338 | if normal_info.TypeId == 'Sketcher::SketchObject':
339 | b = App.Vector(0, 0, 1)
340 | return normal_info.Placement.Rotation.multVec(b)
341 | else:
342 | # this is a draft element or something else:
343 | # no information for the normals of the path
344 | if abs(n.x) <= abs(n.y):
345 | if abs(n.x) <= abs(n.z):
346 | a = App.Vector(1, 0, 0)
347 | else:
348 | a = App.Vector(0, 0, 1)
349 | else:
350 | if abs(n.y) <= abs(n.z):
351 | a = App.Vector(0, 1, 0)
352 | else:
353 | a = App.Vector(0, 0, 1)
354 | print(n)
355 | print(a)
356 | b = n.cross(a).normalize()
357 | return b
358 |
359 |
360 | def project_to_plane(plane_p, plane_n, p, n):
361 | '''p + l * n = plane_p + x * plane_n'''
362 | return p + n * (plane_n.dot(plane_p - p) / (n.dot(plane_n)))
363 |
364 |
365 | def to_np(app_vec):
366 | return np.array([app_vec.x, app_vec.y, app_vec.z])
367 |
368 |
--------------------------------------------------------------------------------
/freecad/frametools/icons/beam_plane_cut.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
256 |
--------------------------------------------------------------------------------
/freecad/frametools/icons/linked_face.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
255 |
--------------------------------------------------------------------------------
/freecad/frametools/icons/reload.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
371 |
--------------------------------------------------------------------------------
/freecad/frametools/icons/beam_miter_cut.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
263 |
--------------------------------------------------------------------------------
/freecad/frametools/icons/screw.svg:
--------------------------------------------------------------------------------
1 |
2 |
204 |
--------------------------------------------------------------------------------
/freecad/frametools/icons/beam_shape_cut.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
269 |
--------------------------------------------------------------------------------
/freecad/frametools/icons/generic_solver.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
397 |
--------------------------------------------------------------------------------
/freecad/frametools/interaction.py:
--------------------------------------------------------------------------------
1 | from __future__ import division
2 | import FreeCAD as App
3 | import FreeCADGui as Gui
4 | import numpy as np
5 | import os
6 | import Part
7 | from PySide import QtGui
8 |
9 | from . import beamobj
10 |
11 | __all__ = [
12 | "make_beam",
13 | "make_miter_cut",
14 | "make_plane_cut",
15 | "make_shape_cut"]
16 |
17 |
18 | def refresh():
19 | reload(beamobj)
20 |
21 |
22 | class make_beam(object):
23 |
24 | def __init__(self, view):
25 | self.profile = None
26 | self.path = None
27 | self.view = view
28 | App.Console.PrintMessage("choose the profile\n")
29 | Gui.Selection.clearSelection()
30 |
31 | self.observer = Gui.Selection.addObserver(self)
32 |
33 | self.form = Gui.PySideUic.loadUi(os.path.join(os.path.dirname(__file__), "make_beam.ui"))
34 | self.form.buttonProfile.setChecked(True)
35 | self.form.buttonProfile.clicked.connect(self.selectProfile)
36 | self.form.buttonPaths.clicked.connect(self.selectPaths)
37 | self.form.buttonCreate.clicked.connect(self.createBeam)
38 | self.form.buttonCreate.setEnabled(False)
39 |
40 | Gui.Control.showDialog(self)
41 |
42 | def selectProfile(self):
43 | if self.form.buttonProfile.isChecked():
44 | self.form.buttonPaths.setChecked(False)
45 | Gui.Selection.clearSelection()
46 |
47 | def selectPaths(self):
48 | if self.form.buttonPaths.isChecked():
49 | self.form.buttonProfile.setChecked(False)
50 | Gui.Selection.clearSelection()
51 |
52 | def addSelection(self, document, object, subobject, pos):
53 | if self.form.buttonProfile.isChecked():
54 | self.profile = App.getDocument(document).getObject(object)
55 | self.form.lineProfile.setText(self.profile.Label)
56 | self.form.buttonProfile.setChecked(False)
57 | self.form.buttonPaths.setChecked(True)
58 | Gui.Selection.clearSelection()
59 | App.Console.PrintMessage("choose_path\n")
60 | else:
61 | if subobject != "":
62 | Gui.Selection.clearSelection()
63 | self.path = (document, object, subobject)
64 |
65 | (document, object, subobject) = self.path
66 | obj = App.getDocument(document).getObject(object)
67 |
68 | if obj != None:
69 | self.form.path.setText("{}.{}".format(obj.Label, subobject))
70 |
71 | self.form.buttonCreate.setEnabled((self.profile != None) and (self.path != None))
72 |
73 | def createBeam(self):
74 | (document, object, subobject) = self.path
75 | path_sketch = App.getDocument(document).getObject(object)
76 |
77 | if object != None:
78 | a = App.ActiveDocument.addObject("Part::FeaturePython", "beam")
79 | beamobj.Beam(a, self.profile, path_sketch, subobject)
80 | beamobj.ViewProviderBeam(a.ViewObject)
81 | a.exdent_1 = self.form.extent1.property("value")
82 | a.exdent_2 = self.form.extent2.property("value")
83 | a.Rotation = self.form.rotation.property("value")
84 |
85 | App.ActiveDocument.recompute()
86 |
87 | def getStandardButtons(self):
88 | return int(QtGui.QDialogButtonBox.Close)
89 |
90 | def reject(self):
91 | Gui.Selection.removeObserver(self)
92 | Gui.Control.closeDialog()
93 |
94 |
95 | class make_miter_cut(object):
96 |
97 | def __init__(self, view):
98 | self.view = view
99 | self.beam_1 = None
100 | self.beam_2 = None
101 |
102 | Gui.Selection.addObserver(self)
103 |
104 | self.form = Gui.PySideUic.loadUi(os.path.join(os.path.dirname(__file__), "miter-cut.ui"))
105 | self.form.setProperty("windowTitle", "Miter Cut")
106 | self.form.buttonBeam1.setChecked(True)
107 | self.form.buttonBeam1.clicked.connect(self.selectBeam1)
108 | self.form.buttonBeam2.clicked.connect(self.selectBeam2)
109 | self.form.buttonCut.clicked.connect(self.createCut)
110 | self.form.buttonCut.setEnabled(False)
111 |
112 | items = Gui.Selection.getSelectionEx()
113 | if (len(items) > 0) and (items[0].Object.Proxy != None) \
114 | and ((items[0].Object.Proxy.__class__.__name__ == 'Beam') or (items[0].Object.Proxy.__class__.__name__ == 'CBeam')):
115 | self.addSelection(items[0].DocumentName, items[0].ObjectName, None, None)
116 | if (len(items) > 1) and (items[1].Object.Proxy != None) \
117 | and ((items[1].Object.Proxy.__class__.__name__ == 'Beam') or (items[1].Object.Proxy.__class__.__name__ == 'CBeam')):
118 | self.addSelection(items[1].DocumentName, items[1].ObjectName, None, None)
119 |
120 | Gui.Control.showDialog(self)
121 | Gui.Selection.clearSelection()
122 |
123 | def set_miter_cut_obj(self, obj, beam):
124 | if not obj.cut_type:
125 | obj.cut_obj = beam
126 | obj.cut_type = "miter"
127 | return
128 | return False
129 |
130 | def selectBeam1(self):
131 | if self.form.buttonBeam1.isChecked():
132 | self.form.buttonBeam2.setChecked(False)
133 | Gui.Selection.clearSelection()
134 |
135 | def selectBeam2(self):
136 | if self.form.buttonBeam2.isChecked():
137 | self.form.buttonBeam1.setChecked(False)
138 | Gui.Selection.clearSelection()
139 |
140 | def addSelection(self, document, object, subobject, pos):
141 | obj = App.getDocument(document).getObject(object)
142 | name = "{}.{}".format(document, obj.Label)
143 |
144 | if self.form.buttonBeam1.isChecked():
145 | self.beam_1 = obj
146 | self.form.beam1.setText(name)
147 | self.form.buttonBeam2.setChecked(True)
148 | self.selectBeam2()
149 | elif self.form.buttonBeam2.isChecked():
150 | self.beam_2 = obj
151 | self.form.beam2.setText(name)
152 |
153 | messages = ""
154 | if (self.beam_1 != None) and (self.beam_1.Proxy.__class__.__name__ != "Beam") and (self.beam_1.Proxy.__class__.__name__ != "CBeam"):
155 | messages = messages + "The object {}.{} is not of beam type\n".format(self.beam_1.Document.Name, self.beam_1.Label)
156 | if (self.beam_2 != None) and (self.beam_2.Proxy.__class__.__name__ != "Beam") and (self.beam_2.Proxy.__class__.__name__ != "CBeam"):
157 | messages = messages + "The object {}.{} is not of beam type\n".format(self.beam_2.Document.Name, self.beam_2.Label)
158 |
159 | self.form.validationMessage.setText(messages)
160 | self.form.buttonCut.setEnabled((self.beam_1 != None) and (self.beam_2 != None) and (self.beam_1 != self.beam_2) and (len(messages) == 0))
161 |
162 | def createCut(self):
163 | a = App.ActiveDocument.addObject(
164 | "Part::FeaturePython", "miter_beam")
165 | beamobj.CBeam(a, self.beam_1)
166 | self.set_miter_cut_obj(a, self.beam_2)
167 | beamobj.ViewProviderCBeam(a.ViewObject)
168 |
169 | a = App.ActiveDocument.addObject(
170 | "Part::FeaturePython", "miter_beam")
171 | beamobj.CBeam(a, self.beam_2)
172 | self.set_miter_cut_obj(a, self.beam_1)
173 | beamobj.ViewProviderCBeam(a.ViewObject)
174 |
175 | self.form.buttonBeam1.setChecked(True)
176 | self.selectBeam1()
177 | App.ActiveDocument.recompute()
178 |
179 | def getStandardButtons(self):
180 | return int(QtGui.QDialogButtonBox.Close)
181 |
182 | def reject(self):
183 | Gui.Selection.removeObserver(self)
184 | Gui.Control.closeDialog()
185 |
186 |
187 | def to_np(app_vec):
188 | return np.array([app_vec.x, app_vec.y, app_vec.z])
189 |
190 |
191 | class make_plane_cut(object):
192 |
193 | def __init__(self, view):
194 | self.view = view
195 | self.beam = None
196 | self.cut_beam = None
197 | self.cut_face = None
198 | Gui.Selection.addObserver(self)
199 |
200 | self.form = Gui.PySideUic.loadUi(os.path.join(os.path.dirname(__file__), "plane-cut.ui"))
201 | self.form.setProperty("windowTitle", "Plane Cut")
202 | self.form.buttonBeam.setChecked(True)
203 | self.form.buttonBeam.clicked.connect(self.selectBeam)
204 | self.form.buttonFace.clicked.connect(self.selectFace)
205 | self.form.buttonCut.clicked.connect(self.createCut)
206 | self.form.buttonCut.setEnabled(False)
207 |
208 | items = Gui.Selection.getSelectionEx()
209 | if (len(items) > 0) and (items[0].Object.Proxy != None) and ((items[0].Object.Proxy.__class__.__name__ == 'Beam') or (items[0].Object.Proxy.__class__.__name__ == 'CBeam')):
210 | self.addSelection(items[0].DocumentName, items[0].ObjectName, None, None)
211 | if (len(items) > 1) and (items[1].Object.Proxy != None) \
212 | and ((items[1].Object.Proxy.__class__.__name__ == 'Beam') or (items[1].Object.Proxy.__class__.__name__ == 'CBeam')) \
213 | and (len(items[1].SubElementNames) > 0):
214 | self.addSelection(items[1].DocumentName, items[1].ObjectName, items[1].SubElementNames[0], None)
215 |
216 | Gui.Control.showDialog(self)
217 | Gui.Selection.clearSelection()
218 |
219 | def set_cut_obj(self, obj, beam, face):
220 | if not obj.cut_type:
221 | obj.cut_obj = beam
222 | obj.cut_type = "cut"
223 | obj.cut_obj_name = face
224 | return
225 | return False
226 |
227 | def selectBeam(self):
228 | if self.form.buttonBeam.isChecked():
229 | self.form.buttonFace.setChecked(False)
230 | Gui.Selection.clearSelection()
231 |
232 | def selectFace(self):
233 | if self.form.buttonFace.isChecked():
234 | self.form.buttonBeam.setChecked(False)
235 | Gui.Selection.clearSelection()
236 |
237 | def addSelection(self, document, object, subobject, pos):
238 | obj = App.getDocument(document).getObject(object)
239 |
240 | if self.form.buttonBeam.isChecked():
241 | self.beam = obj
242 | self.form.beam.setText("{}.{}".format(document, obj.Label))
243 | self.form.buttonFace.setChecked(True)
244 | self.selectFace()
245 | elif self.form.buttonFace.isChecked() and (subobject != None):
246 | self.cut_beam = obj
247 | self.cut_face = subobject
248 | self.form.face.setText("{}.{}.{}".format(document, obj.Label, subobject))
249 |
250 | messages = ""
251 | if (self.beam != None) and (self.beam.Proxy.__class__.__name__ != "Beam") and (self.beam.Proxy.__class__.__name__ != "CBeam"):
252 | messages = messages + "The object {}.{} is not of beam type\n".format(self.beam.Document.Name, self.beam.Label)
253 | if (self.cut_beam != None) and (self.cut_beam.Proxy.__class__.__name__ != "Beam") and (self.cut_beam.Proxy.__class__.__name__ != "CBeam"):
254 | messages = messages + "The object {}.{} is not of beam type\n".format(self.cut_beam.Document.Name, self.cut_beam.Name)
255 |
256 | self.form.validationMessage.setText(messages)
257 | self.form.buttonCut.setEnabled((self.beam != None) and (self.cut_beam != None) and (self.beam != self.cut_beam) and (len(messages) == 0))
258 |
259 | def createCut(self):
260 | a = App.ActiveDocument.addObject(
261 | "Part::FeaturePython", "cut_beam")
262 | beamobj.CBeam(a, self.beam)
263 | self.set_cut_obj(a, self.cut_beam, self.cut_face)
264 | beamobj.ViewProviderCBeam(a.ViewObject)
265 |
266 | self.form.buttonBeam.setChecked(True)
267 | self.selectBeam()
268 | App.ActiveDocument.recompute()
269 |
270 | def getStandardButtons(self):
271 | return int(QtGui.QDialogButtonBox.Close)
272 |
273 | def reject(self):
274 | Gui.Selection.removeObserver(self)
275 | Gui.Control.closeDialog()
276 |
277 |
278 | class make_shape_cut(object):
279 |
280 | def __init__(self, view):
281 | self.view = view
282 | self.beam_1 = None
283 | self.beam_2 = None
284 |
285 | Gui.Selection.addObserver(self)
286 |
287 | self.form = Gui.PySideUic.loadUi(os.path.join(os.path.dirname(__file__), "shape-cut.ui"))
288 | self.form.setProperty("windowTitle", "Shape Cut")
289 | self.form.buttonBeam1.setChecked(True)
290 | self.form.buttonBeam1.clicked.connect(self.selectBeam1)
291 | self.form.buttonBeam2.clicked.connect(self.selectBeam2)
292 | self.form.buttonCut.clicked.connect(self.createCut)
293 | self.form.buttonCut.setEnabled(False)
294 |
295 | items = Gui.Selection.getSelectionEx()
296 | if (len(items) > 0) and (items[0].Object.Proxy != None) \
297 | and ((items[0].Object.Proxy.__class__.__name__ == 'Beam') or (items[0].Object.Proxy.__class__.__name__ == 'CBeam')):
298 | self.addSelection(items[0].DocumentName, items[0].ObjectName, None, None)
299 | if (len(items) > 1) and (items[1].Object.Proxy != None) \
300 | and ((items[1].Object.Proxy.__class__.__name__ == 'Beam') or (items[1].Object.Proxy.__class__.__name__ == 'CBeam')):
301 | self.addSelection(items[1].DocumentName, items[1].ObjectName, None, None)
302 |
303 | Gui.Control.showDialog(self)
304 | Gui.Selection.clearSelection()
305 |
306 | def set_cut_obj(self, obj, beam):
307 | if not obj.cut_type:
308 | obj.cut_obj = beam
309 | obj.cut_type = "shape_cut"
310 | return
311 | return False
312 |
313 | def selectBeam1(self):
314 | if self.form.buttonBeam1.isChecked():
315 | self.form.buttonBeam2.setChecked(False)
316 | Gui.Selection.clearSelection()
317 |
318 | def selectBeam2(self):
319 | if self.form.buttonBeam2.isChecked():
320 | self.form.buttonBeam1.setChecked(False)
321 | Gui.Selection.clearSelection()
322 |
323 | def addSelection(self, document, object, subobject, pos):
324 | obj = App.getDocument(document).getObject(object)
325 | name = "{}.{}".format(document, obj.Label)
326 |
327 | if self.form.buttonBeam1.isChecked():
328 | self.beam_1 = obj
329 | self.form.beam1.setText(name)
330 | self.form.buttonBeam2.setChecked(True)
331 | self.selectBeam2()
332 | elif self.form.buttonBeam2.isChecked():
333 | self.beam_2 = obj
334 | self.form.beam2.setText(name)
335 |
336 | messages = ""
337 | if (self.beam_1 != None) and (self.beam_1.Proxy.__class__.__name__ != "Beam") and (self.beam_1.Proxy.__class__.__name__ != "CBeam"):
338 | messages = messages + "The object {}.{} is not of beam type\n".format(self.beam_1.Document.Name, self.beam_1.Label)
339 | if (self.beam_2 != None) and (self.beam_2.Proxy.__class__.__name__ != "Beam") and (self.beam_2.Proxy.__class__.__name__ != "CBeam"):
340 | messages = messages + "The object {}.{} is not of beam type\n".format(self.beam_2.Document.Name, self.beam_2.Label)
341 |
342 | self.form.validationMessage.setText(messages)
343 | self.form.buttonCut.setEnabled((self.beam_1 != None) and (self.beam_2 != None) and (self.beam_1 != self.beam_2) and (len(messages) == 0))
344 |
345 | def createCut(self):
346 | a = App.ActiveDocument.addObject(
347 | "Part::FeaturePython", "shape_cut_beam")
348 | beamobj.CBeam(a, self.beam_1)
349 | self.set_cut_obj(a, self.beam_2)
350 | beamobj.ViewProviderCBeam(a.ViewObject)
351 |
352 | Gui.Selection.clearSelection()
353 | self.form.buttonBeam1.setChecked(True)
354 | self.selectBeam1()
355 | App.ActiveDocument.recompute()
356 |
357 | def getStandardButtons(self):
358 | return int(QtGui.QDialogButtonBox.Close)
359 |
360 | def reject(self):
361 | Gui.Selection.removeObserver(self)
362 | Gui.Control.closeDialog()
363 |
--------------------------------------------------------------------------------