├── .gitattributes
├── .gitignore
├── Init.py
├── InitGui.py
├── LICENSE
├── PartOMagic
├── Base
│ ├── Compatibility.py
│ ├── Containers.py
│ ├── ExpressionParser.py
│ ├── FilePlant
│ │ ├── Errors.py
│ │ ├── FCObject.py
│ │ ├── FCProject.py
│ │ ├── FCProperty.py
│ │ ├── Misc.py
│ │ ├── ObjectMaker.py
│ │ ├── PropertyExpressionEngine.py
│ │ ├── __init__.py
│ │ ├── importFCStd.py
│ │ ├── readme.md
│ │ └── xmls
│ │ │ ├── Document_expressions.xml
│ │ │ ├── Document_full.xml
│ │ │ ├── Document_links.xml
│ │ │ ├── Document_links_empty.xml
│ │ │ ├── Document_short.xml
│ │ │ ├── GuiDocument.xml
│ │ │ └── Persistence.xml
│ ├── LinkTools.py
│ ├── Parameters.py
│ ├── Utils.py
│ └── __init__.py
├── Features
│ ├── AssyFeatures
│ │ ├── Instance.py
│ │ ├── MuxAssembly.py
│ │ └── __init__.py
│ ├── Exporter.py
│ ├── GenericContainer.py
│ ├── Ghost.py
│ ├── Module.py
│ ├── PDShapeFeature.py
│ ├── PartContainer.py
│ ├── PartDesign
│ │ ├── PDShapeFeature.py
│ │ └── __init__.py
│ ├── ShapeBinder.py
│ ├── ShapeGroup.py
│ └── __init__.py
├── Gui
│ ├── AACommand.py
│ ├── CommandCollection1.py
│ ├── Control
│ │ ├── OnOff.py
│ │ └── __init__.py
│ ├── GlobalToolbar.py
│ ├── GroupCommand.py
│ ├── Icons
│ │ ├── Icons.py
│ │ ├── Icons.qrc
│ │ ├── __init__.py
│ │ ├── icons
│ │ │ ├── PartOMagic.svg
│ │ │ ├── PartOMagic_DisableObserver.svg
│ │ │ ├── PartOMagic_Duplicate.svg
│ │ │ ├── PartOMagic_EnableObserver.svg
│ │ │ ├── PartOMagic_Enter.svg
│ │ │ ├── PartOMagic_Ghost.svg
│ │ │ ├── PartOMagic_Leave.svg
│ │ │ ├── PartOMagic_ListUsages.svg
│ │ │ ├── PartOMagic_MUX.svg
│ │ │ ├── PartOMagic_MorphContainer.svg
│ │ │ ├── PartOMagic_PDShapeFeature_Additive.svg
│ │ │ ├── PartOMagic_PDShapeFeature_Subtractive.svg
│ │ │ ├── PartOMagic_PauseObserver.svg
│ │ │ ├── PartOMagic_Power.svg
│ │ │ ├── PartOMagic_ReplaceObject.svg
│ │ │ ├── PartOMagic_Select_All.svg
│ │ │ ├── PartOMagic_Select_BSwap.svg
│ │ │ ├── PartOMagic_Select_Children.svg
│ │ │ ├── PartOMagic_Select_ChildrenRecursive.svg
│ │ │ ├── PartOMagic_Select_Invert.svg
│ │ │ ├── PartOMagic_ShapeGroup.svg
│ │ │ ├── PartOMagic_SnapView.svg
│ │ │ ├── PartOMagic_TransferObject.svg
│ │ │ └── PartOMagic_XRay.svg
│ │ └── recompile_rc.bat
│ ├── LinkTools
│ │ ├── ListUsages.py
│ │ ├── ReplaceObject.py
│ │ ├── TaskReplace.py
│ │ ├── TaskReplace.ui
│ │ └── __init__.py
│ ├── MonkeyPatches.py
│ ├── Observer.py
│ ├── TempoVis.py
│ ├── Tools
│ │ ├── Duplicate.py
│ │ ├── LeaveEnter.py
│ │ ├── MorphContainer.py
│ │ ├── SelectionTools.py
│ │ ├── Tip.py
│ │ ├── TransferObject.py
│ │ └── __init__.py
│ ├── Utils.py
│ ├── View
│ │ ├── SnapView.py
│ │ ├── XRay.py
│ │ └── __init__.py
│ └── __init__.py
└── __init__.py
├── README.md
└── package.xml
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
4 | # Custom for Visual Studio
5 | *.cs diff=csharp
6 |
7 | # Standard to msysgit
8 | *.doc diff=astextplain
9 | *.DOC diff=astextplain
10 | *.docx diff=astextplain
11 | *.DOCX diff=astextplain
12 | *.dot diff=astextplain
13 | *.DOT diff=astextplain
14 | *.pdf diff=astextplain
15 | *.PDF diff=astextplain
16 | *.rtf diff=astextplain
17 | *.RTF diff=astextplain
18 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | env/
12 | build/
13 | develop-eggs/
14 | dist/
15 | downloads/
16 | eggs/
17 | .eggs/
18 | lib/
19 | lib64/
20 | parts/
21 | sdist/
22 | var/
23 | *.egg-info/
24 | .installed.cfg
25 | *.egg
26 |
27 | # PyInstaller
28 | # Usually these files are written by a python script from a template
29 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
30 | *.manifest
31 | *.spec
32 |
33 | # Installer logs
34 | pip-log.txt
35 | pip-delete-this-directory.txt
36 |
37 | # Unit test / coverage reports
38 | htmlcov/
39 | .tox/
40 | .coverage
41 | .coverage.*
42 | .cache
43 | nosetests.xml
44 | coverage.xml
45 | *,cover
46 |
47 | # Translations
48 | *.mo
49 | *.pot
50 |
51 | # Django stuff:
52 | *.log
53 |
54 | # Sphinx documentation
55 | docs/_build/
56 |
57 | # PyBuilder
58 | target/
59 |
60 | # =========================
61 | # Operating System Files
62 | # =========================
63 |
64 | # OSX
65 | # =========================
66 |
67 | .DS_Store
68 | .AppleDouble
69 | .LSOverride
70 |
71 | # Thumbnails
72 | ._*
73 |
74 | # Files that might appear in the root of a volume
75 | .DocumentRevisions-V100
76 | .fseventsd
77 | .Spotlight-V100
78 | .TemporaryItems
79 | .Trashes
80 | .VolumeIcon.icns
81 |
82 | # Directories potentially created on remote AFP share
83 | .AppleDB
84 | .AppleDesktop
85 | Network Trash Folder
86 | Temporary Items
87 | .apdisk
88 |
89 | # Windows
90 | # =========================
91 |
92 | # Windows image file caches
93 | Thumbs.db
94 | ehthumbs.db
95 |
96 | # Folder config file
97 | Desktop.ini
98 |
99 | # Recycle Bin used on file shares
100 | $RECYCLE.BIN/
101 |
102 | # Windows Installer files
103 | *.cab
104 | *.msi
105 | *.msm
106 | *.msp
107 |
108 | # Windows shortcuts
109 | *.lnk
110 |
--------------------------------------------------------------------------------
/Init.py:
--------------------------------------------------------------------------------
1 | FreeCAD.addImportType("FreeCAD file (PoM) (*.FCStdM)","PartOMagic.Base.FilePlant.importFCStd")
2 | FreeCAD.addExportType("FreeCAD file (PoM) (*.FCStdM)","PartOMagic.Base.FilePlant.importFCStd")
3 |
--------------------------------------------------------------------------------
/InitGui.py:
--------------------------------------------------------------------------------
1 |
2 | import FreeCAD as App
3 |
4 | import PartOMagic.Base.Parameters as Params
5 |
6 | if Params.EnablePartOMagic.get():
7 | import PartOMagic.Base.Compatibility as compat
8 | try:
9 | compat.check_POM_compatible()
10 | import PartOMagic
11 | PartOMagic.importAll()
12 | Gui.addModule("PartOMagic")
13 | if Params.EnableObserver.get():
14 | PartOMagic.Gui.Observer.start()
15 | except compat.CompatibilityError as err:
16 | Params.EnablePartOMagic.set_volatile(False)
17 | App.Console.PrintError(u"Part-o-magic is disabled.\n {err}".format(err= str(err)))
18 |
19 | if Params.EnablePartOMagic.get():
20 | try:
21 | import Show.Containers
22 | # good tempovis, do not replace.
23 | except ImportError:
24 | # old TempoVis
25 | # substitute TempoVis's isContainer with a more modern one
26 | import Show.TempoVis
27 | Show.TempoVis = PartOMagic.Gui.TempoVis.TempoVis
28 |
29 | if Params.EnablePartOMagic.get():
30 | # global toolbar - update only if missing
31 | if not PartOMagic.Gui.GlobalToolbar.isRegistered():
32 | PartOMagic.Gui.GlobalToolbar.registerToolbar()
33 | # PartDesign toolbar - always update
34 | PartOMagic.Gui.GlobalToolbar.registerPDToolbar()
35 |
36 | class PartOMagicWorkbench (Workbench):
37 | MenuText = 'Part-o-magic'
38 | ToolTip = "Part-o-magic: experimental group and Part and Body automation"
39 |
40 | def __init__(self):
41 | # Hack: obtain path to POM by loading a dummy Py module
42 | import os
43 | import PartOMagic
44 | self.__class__.Icon = os.path.dirname(PartOMagic.__file__) + u"/Gui/Icons/icons/PartOMagic.svg".replace("/", os.path.sep)
45 |
46 | def Initialize(self):
47 | import PartOMagic as POM
48 | POM.importAll()
49 |
50 | cmdsControl = ([]
51 | + POM.Gui.Control.exportedCommands()
52 | )
53 | self.appendMenu('Part-o-Magic', cmdsControl)
54 | cmdsControl.remove('PartOMagic_Power') #don't add this command to toolbar
55 | self.appendToolbar('POMControl', cmdsControl)
56 |
57 | self.appendMenu('Part-o-Magic', ["Separator"])
58 |
59 | cmdsNewContainers = ([]
60 | + POM.Features.exportedCommands()
61 | + POM.Features.PartDesign.exportedCommands()
62 | )
63 | self.appendToolbar('POMContainers', cmdsNewContainers)
64 | self.appendMenu('Part-o-Magic', cmdsNewContainers)
65 |
66 | self.appendMenu('Part-o-Magic', ["Separator"])
67 |
68 | cmdsTools = ([]
69 | + POM.Gui.Tools.exportedCommands()
70 | )
71 | self.appendToolbar('POMTools', cmdsTools)
72 | self.appendMenu('Part-o-Magic', cmdsTools)
73 |
74 | self.appendMenu('Part-o-Magic', ["Separator"])
75 |
76 | cmdsLinkTools = ([]
77 | + POM.Gui.LinkTools.exportedCommands()
78 | )
79 | self.appendToolbar('POMLinkTools', cmdsLinkTools)
80 | self.appendMenu('Part-o-Magic', cmdsLinkTools)
81 |
82 |
83 | self.appendMenu('Part-o-Magic', ["Separator"])
84 |
85 | cmdsAssy = ([]
86 | + POM.Features.AssyFeatures.exportedCommands()
87 | )
88 | self.appendToolbar('POMAssembly', cmdsAssy)
89 | self.appendMenu('Part-o-Magic', cmdsAssy)
90 |
91 |
92 | self.appendMenu('Part-o-Magic', ["Separator"])
93 |
94 | cmdsView = ([]
95 | + POM.Gui.View.exportedCommands()
96 | )
97 | self.appendToolbar('POMView', cmdsView)
98 | self.appendMenu('Part-o-Magic', cmdsView)
99 |
100 |
101 | def Activated(self):
102 | pass
103 |
104 | if Params.EnablePartOMagic.get():
105 | Gui.addWorkbench(PartOMagicWorkbench())
106 |
107 |
108 |
109 |
--------------------------------------------------------------------------------
/PartOMagic/Base/Compatibility.py:
--------------------------------------------------------------------------------
1 | import FreeCAD as App
2 |
3 | def get_fc_version():
4 | """returns tuple like (0,18,4,16154) for 0.18.4 release, and (0,19,0,18234) for pre builds"""
5 | # ['0', '18', '4 (GitTag)', 'git://github.com/FreeCAD/FreeCAD.git releases/FreeCAD-0-18', '2019/10/22 16:53:35', 'releases/FreeCAD-0-18', '980bf9060e28555fecd9e3462f68ca74007b70f8']
6 | # ['0', '19', '18234 (Git)', 'git://github.com/FreeCAD/FreeCAD.git master', '2019/09/15 20:43:17', 'master', '3af5d97e9b2a60823815f662aba25422c4bc45bb']
7 | # ['0', '21', '0', '32457 (Git)', 'https://github.com/FreeCAD/FreeCAD master', '2023/03/23 00:09:35', 'master', '85216bd12730bbc4c3cbf8f0bc50416ab1556cbb']
8 | if len(App.Version()) <= 7:
9 | strmaj, strmi, strrev = App.Version()[0:3]
10 | maj, mi = int(strmaj), int(strmi)
11 | submi, rev = 0, 0
12 | elif len(App.Version()) >= 8:
13 | strmaj, strmi, strsubmi, strrev = App.Version()[0:4]
14 | maj, mi, submi = int(strmaj), int(strmi), int(strsubmi)
15 | rev = 0
16 | if '(GitTag)' in strrev:
17 | submi = int(strrev.split(" ")[0])
18 | elif '(Git)' in strrev:
19 | try:
20 | rev = int(strrev.split(" ")[0])
21 | except Exception as err:
22 | App.Console.PrintWarning(u"PartOMagic failed to detect FC version number.\n"
23 | " {err}\n".format(err= str(err)))
24 | rev = 32457 #assume fairly modern
25 | if rev < 100:
26 | if mi == 17:
27 | rev = 13544
28 | elif mi == 18:
29 | rev = 16154
30 | elif mi == 19:
31 | rev = 24276
32 | elif mi == 20:
33 | rev = 29177
34 | else:
35 | rev = 32457 #assume fairly modern
36 | App.Console.PrintWarning(u"PartOMagic failed to detect FC version number: revision is zero / too low, minor version is unexpected.")
37 | return (maj, mi, submi, rev)
38 |
39 |
40 | def get_fc_revision_nr():
41 | return get_fc_version()[3]
42 |
43 | def check_POM_compatible():
44 | """Raises CompatibilityError if PoM is known to not run"""
45 | try:
46 | rev = get_fc_revision_nr()
47 | except Exception as err:
48 | App.Console.PrintWarning(u"PartOMagic failed to detect FC version number.\n"
49 | " {err}\n".format(err= str(err)))
50 | #keep going, assume the version is good enough...
51 | return
52 |
53 | if rev < 9933:
54 | raise CompatibilityError("Part-o-magic requires FreeCAD at least v0.17.9933. Yours appears to have a rev.{rev}, which is less.".format(rev= rev))
55 |
56 | def scoped_links_are_supported():
57 | try:
58 | return get_fc_revision_nr() >= 12027
59 | except Exception as err:
60 | return True #assume good
61 |
62 | def tempovis_is_stacky():
63 | try:
64 | import Show.SceneDetail
65 | except ImportError:
66 | return False
67 | else:
68 | return True
69 |
70 | class CompatibilityError(RuntimeError):
71 | pass
72 |
--------------------------------------------------------------------------------
/PartOMagic/Base/ExpressionParser.py:
--------------------------------------------------------------------------------
1 | __doc__ = "ExpressionParser: minimalistic routines for extracting information from expressions"
2 |
3 | namechars = [chr(c) for c in range(ord('a'), ord('z')+1)]
4 | namechars += [chr(c) for c in range(ord('A'), ord('Z')+1)]
5 | namechars += [chr(c) for c in range(ord('0'), ord('9')+1)]
6 | namechars += ['_']
7 | namechars = set(namechars)
8 |
9 | def expressionDeps(expr, doc):
10 | """expressionDeps(expr, doc): returns set of objects referenced by the expression, as list of Relations with unfilled linking_object.
11 | expr: expression, as a string
12 | doc: document where to look up objects by names/labels.
13 | If doc is None, all names are assumed valid. A list of tuples is returned instead of list
14 | of relations: [(name_or_label, (start, end_plus_1))]. This may falsely recognize property
15 | self-references as objects, for example in '=Placement.Base.x' will list 'Placement'
16 | as an object. """
17 | global namechars
18 | startchars = set("+-*/(%^&\[<>;, =")
19 | endchars = set(".")
20 |
21 | if expr is None:
22 | return []
23 |
24 | ids = [] #list of tuples: (identifier, (start, end_plus_1))
25 | start = 0
26 | finish = 0
27 | for i in range(len(expr)):
28 | if expr[i] in startchars:
29 | start = i+1
30 | elif expr[i] in endchars:
31 | finish = i
32 | if finish - start > 0:
33 | ids.append((expr[start:finish], (start, finish)))
34 | start = len(expr)
35 | elif expr[i] not in namechars:
36 | finish = i
37 | start = len(expr)
38 |
39 | if doc is None:
40 | #nowhere to look up. Returning the simplified variant:
41 | return ids #list of tuples, [(name_or_label, (start, end_plus_1))]
42 | else:
43 | from .LinkTools import Relation
44 | #look up objects
45 | ret = []
46 | for id, id_range in ids:
47 | # try by name
48 | obj = doc.getObject(id)
49 | if obj is None:
50 | # try by label
51 | objs = doc.getObjectsByLabel(id)
52 | if len(objs) == 1:
53 | obj = objs[0]
54 | if obj is not None:
55 | ret.append(Relation(None, 'Expression', None, obj, expression_charrange= id_range))
56 | else:
57 | print(u"identifier in expression not recognized: {id}".format(id= id))
58 |
59 | return ret
60 |
--------------------------------------------------------------------------------
/PartOMagic/Base/FilePlant/Errors.py:
--------------------------------------------------------------------------------
1 |
2 | class FileplantError(RuntimeError):
3 | pass
4 | class NameCollisionError(FileplantError):
5 | pass
6 | class ObjectNotFoundError(KeyError):
7 | pass
8 | class PropertyNotFoundError(AttributeError):
9 | pass
10 |
11 |
12 | def warn(message):
13 | import sys
14 | freecad = sys.modules.get('FreeCAD', None)
15 | if freecad is None:
16 | print(message)
17 | else:
18 | freecad.Console.PrintWarning(message + '\n')
--------------------------------------------------------------------------------
/PartOMagic/Base/FilePlant/FCObject.py:
--------------------------------------------------------------------------------
1 | import zipfile
2 | from xml.etree import ElementTree
3 | import io
4 |
5 | from .Errors import *
6 | from . import FCProperty
7 |
8 | content_base_xml = (
9 | """
10 |
11 | """
12 | )
13 |
14 |
15 | #objectnode:
16 | #
17 |
18 | #datanode:
19 | #