├── .gitignore ├── LICENSE ├── README.md ├── __init__.py ├── blender_helper.py ├── changelog.md ├── dev ├── .gitignore ├── forum_post_list_results.md ├── freecad_linking_example │ ├── assembly.FCStd │ ├── assembly.png │ ├── assembly_export.FCStd │ ├── assembly_export.blend │ ├── assembly_tree.png │ ├── base_dimensions.FCStd │ ├── box_panel.FCStd │ ├── front_panel.FCStd │ └── lamp.FCStd ├── freecad_test_ArchWB │ ├── ArchTest.FCStd │ ├── ArchTest.blend │ ├── building.FCStd │ ├── building.blend │ ├── cube_with_color_faces.FCStd │ ├── simple_wall.FCStd │ ├── simple_wall_with_door.FCStd │ ├── simple_wall_with_door.blend │ ├── simple_window.FCStd │ ├── simple_window.blend │ └── test_MultiMaterial.FCStd ├── freecad_test_DraftLinkedArray │ ├── Test_DraftLinkedArray.FCStd │ ├── Test_DraftLinkedArray.png │ ├── Test_DraftLinkedArray_ObjectTree.png │ ├── justopen.py │ └── justopen_copy_paste.py ├── freecad_test_DraftLinkedArray_Visibility │ ├── DraftLinkedArray_Visibility.FCStd │ └── DraftLinkedArray_Visibility2.FCStd ├── freecad_test_MyLittleWorld │ ├── MyLittleWorld.FCStd │ ├── MyLittleWorld.blend │ ├── MyLittleWorld.png │ ├── MyLittleWorld_ObjectTree.png │ ├── list_objects.py │ └── list_objects_copypaste.py ├── freecad_test_ParentChildPositions │ ├── TestParentChildPositions.FCStd │ ├── TestParentChildPositions.blend │ ├── TestParentChildPositions.png │ └── TestParentChildPositions_ObjectTree.png ├── freecad_test_body_objects │ ├── BodyTest.FCStd │ ├── BodyTest.blend │ ├── BodyTest.png │ ├── BodyTest_Minimal.FCStd │ ├── BodyTest_Minimal.png │ ├── BodyTest__ObjectTree.png │ ├── list_objects.py │ ├── list_objects__minimal.py │ ├── list_objects_copypaste.py │ ├── list_objects_copypaste__minimal.py │ └── python_import_debug.blend ├── hdr │ └── hdrihaven.com │ │ └── lakeside_1k.hdr ├── import_tests.py ├── list_objects.py ├── list_objects_simple.py ├── macro_copySimpleExtended.py ├── script_test.blend └── temp_python3_cmd_copyAndpaste.py ├── freecad_helper.py ├── import_fcstd ├── __init__.py ├── guidata.py ├── helper.py └── material.py └── pylama.ini /.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 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | .pytest_cache/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | db.sqlite3 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | # Jupyter Notebook 73 | .ipynb_checkpoints 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # celery beat schedule file 79 | celerybeat-schedule 80 | 81 | # SageMath parsed files 82 | *.sage.py 83 | 84 | # Environments 85 | .env 86 | .venv 87 | env/ 88 | venv/ 89 | ENV/ 90 | env.bak/ 91 | venv.bak/ 92 | 93 | # Spyder project settings 94 | .spyderproject 95 | .spyproject 96 | 97 | # Rope project settings 98 | .ropeproject 99 | 100 | # mkdocs documentation 101 | /site 102 | 103 | # mypy 104 | .mypy_cache/ 105 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Stefan Krüger 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | # io_import_fcstd 5 | blender importer for FreeCAD files 6 | 7 | based on gist [yorikvanhavre/FreeCAD .FCStd importer for Blender 2.80](https://gist.github.com/yorikvanhavre/680156f59e2b42df8f5f5391cae2660b) 8 | 9 | # description 10 | 11 | This script imports FreeCAD .FCStd files into Blender. 12 | This is a work in progress, so not all geometry elements of FreeCAD 13 | might be supported at this point. 14 | And the `update` option will make some mess on older already imported objects.. 15 | 16 | 17 | The development of this addon happens on the FreeCAD forum at 18 | [FreeCAD .FCStd importer for Blender 2.80](https://forum.freecadweb.org/viewtopic.php?f=22&t=39778) 19 | 20 | # download 21 | if you feel comfortable with git clone the repository - 22 | it is the easiest way to stay up to date ;-) 23 | 24 | otherwise you can use the download button to get a zip file. 25 | than you can install it in blender by going to 26 | `edit` - `preferences` - `Add-ons` 27 | and there is a button `Install...` in the top right corner. 28 | select the downloaded zip.. 29 | then enable it (you can search for FreeCAD) 30 | after this please read [setup](#setup) 31 | (check the addon-preferences and set your path to FreeCAD lib folder..) 32 | 33 | # setup 34 | 35 | This addon requires FreeCAD to be installed on your system. 36 | A word of warning, your version of FreeCAD must be compiled 37 | with the same version of python as Blender. 38 | 39 | The first two numbers of the python version must be the same. 40 | For example, if Blender is using Python 3.7.2, your version of FreeCAD must 41 | use Python 3.7 too (the third number after 3.7 can be different) 42 | 43 | Once you have a Python3 version of FreeCAD installed, the FreeCAD 44 | Python module must be known to Blender. 45 | 46 | 47 | Set the correct path to FreeCAD.so (or FreeCAD.pyd on windows) in 48 | the Addons preferences in user settings, there is a setting for 49 | that under the addon panel. 50 | (these settings are only shown if the addon is enabled) 51 | 59 | 60 | 61 | A simple way to test if everything is OK is to enter the following line 62 | in the Python console of Blender. 63 | If no error message appears, everything is fine: 64 | 65 | `import FreeCAD` 66 | 67 | 68 | # working with... 69 | currently i only test under linux 64bit. 70 | please let me know in the issues if it is / is not working for you on other setups. 71 | 72 | 73 | Blender 74 | ``` 75 | version: 3.1.0, branch: master, commit date: 2022-03-08 18:16, hash: c77597cd0e15, type: release 76 | build date: 2022-03-09, 00:34:48 77 | platform: 'Linux-5.11.0-50-generic-x86_64-with-glibc2.33' 78 | ``` 79 | and 80 | FreeCAD 0.19 81 | ```python 82 | OS: Ubuntu 21.04 (KDE/plasma) 83 | Word size of OS: 64-bit 84 | Word size of FreeCAD: 64-bit 85 | Version: 0.19. 86 | Build type: Release 87 | Branch: unknown 88 | Hash: 0d9536ed3e8c7f40197b5606e1b7873625e1d6fe 89 | Python version: 3.9.5 90 | Qt version: 5.15.2 91 | Coin version: 4.0.0 92 | OCC version: 7.5.2 93 | Locale: English/United States (en_US) 94 | 95 | 96 | # build in python console: 97 | Python 3.9.5 (default, Nov 18 2021, 16:00:48) 98 | [GCC 10.3.0] on linux 99 | Type 'help', 'copyright', 'credits' or 'license' for more information. 100 | >>> App.Version()[4] 101 | '2021/07/21 08:10:00' 102 | ``` 103 | and 104 | FreeCAD-daily 0.20 105 | ```python 106 | OS: Ubuntu 21.04 (KDE/plasma) 107 | Word size of FreeCAD: 64-bit 108 | Version: 0.20. 109 | Build type: Release 110 | Branch: unknown 111 | Hash: 4acef3f14fe694f28f7935108d36341b8df83a39 112 | Python version: 3.9.5 113 | Qt version: 5.15.2 114 | Coin version: 4.0.0 115 | OCC version: 7.5.2 116 | Locale: English/United States (en_US) 117 | 118 | # build in python console: 119 | Python 3.9.5 (default, Nov 18 2021, 16:00:48) 120 | [GCC 10.3.0] on linux 121 | >>> App.Version()[4] 122 | '2022/01/26 04:18:00' 123 | ``` 124 | 125 | # TODO 126 | see the [issues for open points](https://github.com/s-light/io_import_fcstd/issues). 127 | 128 | - support clones 129 | - support texts, dimensions, etc (non-part/mesh objects) 130 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- 1 | import math 2 | import bpy 3 | 4 | # ImportHelper is a helper class, defines filename and 5 | # invoke() function which calls the file selector. 6 | # from bpy_extras.io_utils import ImportHelper 7 | # not sure what this brings us... 8 | 9 | from . import import_fcstd 10 | 11 | bl_info = { 12 | "name": "FreeCAD Importer", 13 | "category": "Import-Export", 14 | "author": "Yorik van Havre; Stefan Krüger", 15 | "version": (6, 2, 1), 16 | "blender": (2, 80, 0), 17 | "location": "File > Import > FreeCAD", 18 | "description": "Imports a .FCStd file from FreeCAD", 19 | "warning": ( 20 | "This addon needs FreeCAD installed on your system. " 21 | "Only Part- and Mesh-based objects supported at the moment. " 22 | "It is currently in an Experimental State.." 23 | ), 24 | } 25 | 26 | # brut force path loading: 27 | # import sys; sys.path.append("/path/to/FreeCAD.so") 28 | 29 | 30 | # ============================================================================== 31 | # Blender Operator class 32 | # ============================================================================== 33 | 34 | # https://docs.blender.org/api/current/bpy.types.AddonPreferences.html#module-bpy.types 35 | 36 | 37 | class IMPORT_OT_FreeCAD_Preferences(bpy.types.AddonPreferences): 38 | """A preferences settings dialog to set the path to the FreeCAD module.""" 39 | 40 | # this must match the add-on name, use '__package__' 41 | # when defining this in a submodule of a python package. 42 | # bl_idname = __name__ 43 | bl_idname = __package__ 44 | 45 | # TODO: implement 'auto-magically' finding lib folder 46 | # as a first just implement a list of predefined paths 47 | # and use the first that exists 48 | 49 | # TODO: implement file-choose dialog for the path 50 | 51 | filepath_freecad: bpy.props.StringProperty( 52 | subtype="FILE_PATH", 53 | name="Path to FreeCAD lib", 54 | description=( 55 | "Path to \n" "FreeCAD.so (Mac/Linux) \n" "or \n" "FreeCAD.pyd (Windows)" 56 | ), 57 | default="/usr/lib/freecad-daily-python3/lib/FreeCAD.so", 58 | ) 59 | filepath_system_packages: bpy.props.StringProperty( 60 | subtype="FILE_PATH", 61 | name="Path to system python modules", 62 | description=( 63 | "find with help of python console inside of FreeCAD:\n" 64 | ">>> import six\n" 65 | ">>> six.__file__\n" 66 | "'/usr/lib/python3/dist-packages/six.py'\n" 67 | "use first part: '/usr/lib/python3/dist-packages/'" 68 | ), 69 | default="/usr/lib/python3/dist-packages/", 70 | ) 71 | 72 | def draw(self, context): 73 | """Draw Preferences.""" 74 | layout = self.layout 75 | layout.label( 76 | text=( 77 | "FreeCAD must be installed on your system, and its path set below." 78 | " Make sure both FreeCAD and Blender use the same Python version " 79 | "(check their Python console)" 80 | ) 81 | ) 82 | layout.prop(self, "filepath_freecad") 83 | layout.prop(self, "filepath_system_packages") 84 | 85 | 86 | # class IMPORT_OT_FreeCAD(bpy.types.Operator, ImportHelper): 87 | class IMPORT_OT_FreeCAD(bpy.types.Operator): 88 | """Imports the contents of a FreeCAD .FCStd file.""" 89 | 90 | bl_idname = "io_import_fcstd.import_freecad" 91 | bl_label = "Import FreeCAD FCStd file" 92 | bl_options = {"REGISTER", "UNDO"} 93 | 94 | # ImportHelper mixin class uses this 95 | filename_ext = ".fcstd" 96 | 97 | # https://blender.stackexchange.com/a/7891/16634 98 | # see Text -> Templates -> Python -> Operator File Export 99 | filter_glob: bpy.props.StringProperty( 100 | default="*.FCStd; *.fcstd", options={"HIDDEN"}, 101 | ) 102 | 103 | # Properties assigned by the file selection window. 104 | directory: bpy.props.StringProperty( 105 | maxlen=1024, subtype="FILE_PATH", options={"HIDDEN", "SKIP_SAVE"}, 106 | ) 107 | files: bpy.props.CollectionProperty( 108 | type=bpy.types.OperatorFileListElement, options={"HIDDEN", "SKIP_SAVE"} 109 | ) 110 | 111 | # user import options 112 | option_skiphidden: bpy.props.BoolProperty( 113 | name="Skip hidden objects", 114 | default=True, 115 | description="Only import objects that where visible in FreeCAD", 116 | ) 117 | option_filter_sketch: bpy.props.BoolProperty( 118 | name="Filter Sketch objects", 119 | default=True, 120 | description="Filter Sketch objects out.", 121 | ) 122 | option_update: bpy.props.BoolProperty( 123 | name="Update existing objects", 124 | default=True, 125 | description=( 126 | "Keep objects with same names in current scene and " 127 | "their materials, only replace the geometry" 128 | ), 129 | ) 130 | option_update_only_modified_meshes: bpy.props.BoolProperty( 131 | name="Update only modified meshes", 132 | default=True, 133 | description=( 134 | "Only replace the geometry if the the source in FreeCAD has changed." 135 | ), 136 | ) 137 | option_placement: bpy.props.BoolProperty( 138 | name="Use Placements", 139 | default=True, 140 | description="Set Blender pivot points to the FreeCAD placements", 141 | ) 142 | option_tessellation: bpy.props.FloatProperty( 143 | name="Tessellation value", 144 | default=0.10, 145 | description="The tessellation value to apply when triangulating shapes", 146 | ) 147 | option_auto_smooth_use: bpy.props.BoolProperty( 148 | name="Auto Smooth", 149 | default=True, 150 | description="activate auto_smooth on every imported mesh", 151 | ) 152 | option_auto_smooth_angle: bpy.props.FloatProperty( 153 | name="Auto Smooth Angle", 154 | default=math.radians(85), 155 | soft_min=math.radians(1), 156 | soft_max=math.radians(180), 157 | subtype="ANGLE", 158 | unit="ROTATION", 159 | description="set auto_smooth_angle on every imported mesh", 160 | ) 161 | option_scale: bpy.props.FloatProperty( 162 | name="Scaling value", 163 | precision=4, 164 | default=0.001, 165 | # soft_min=0.0001, 166 | # soft_max=1, 167 | description=( 168 | "A scaling value to apply to imported objects. " 169 | "Default value of 0.001 means one Blender unit = 1 meter" 170 | ), 171 | ) 172 | option_sharemats: bpy.props.BoolProperty( 173 | name="Share similar materials", 174 | default=True, 175 | description=("Objects with same color/transparency will use the same material"), 176 | ) 177 | # option_create_tree: bpy.props.BoolProperty( 178 | # name="Recreate FreeCAD Object-Tree", 179 | # default=True, 180 | # description=( 181 | # "Try to recreate the same parent-child relationships " 182 | # "as in the FreeCAD Object-Tree." 183 | # ) 184 | # ) 185 | option_obj_name_prefix: bpy.props.StringProperty( 186 | name="Prefix object names", 187 | maxlen=42, 188 | default="", 189 | description=("prefix for every object name." ""), 190 | ) 191 | option_prefix_with_filename: bpy.props.BoolProperty( 192 | name="Prefix object names with filename", 193 | default=False, 194 | description=( 195 | "recommend for multi-file import. \n" 196 | "otherwise it can create name confusions." 197 | "" 198 | ), 199 | ) 200 | option_links_as_col: bpy.props.BoolProperty( 201 | name="App::Link as Collection-Instances", 202 | default=False, 203 | description=( 204 | "create App::Link objects as Collection-Instances. \n" 205 | "therefore create Link-Targets as Collections. \n" 206 | "this means the instances can only have the original " 207 | "material of the Link-Target.\n" 208 | "\n" 209 | "if you deactivate this the importer creates `real objects` " 210 | "for every App::Link object - they share the mesh. " 211 | "this can get very deep tree if the Link-Targets are " 212 | "App::Part objects themself.." 213 | "" 214 | ), 215 | ) 216 | 217 | def invoke(self, context, event): 218 | """Invoke is called when the user picks our Import menu entry.""" 219 | context.window_manager.fileselect_add(self) 220 | return {"RUNNING_MODAL"} 221 | 222 | def get_preferences(self): 223 | """Get addon preferences.""" 224 | print("__package__: '{}'".format(__package__)) 225 | user_preferences = bpy.context.preferences 226 | addon_prefs = user_preferences.addons[__package__].preferences 227 | return addon_prefs 228 | 229 | def get_path_to_freecad(self): 230 | """Get FreeCAD path from addon preferences.""" 231 | # get the FreeCAD path specified in addon preferences 232 | addon_prefs = self.get_preferences() 233 | path = addon_prefs.filepath_freecad 234 | print("addon_prefs path_to freecad", path) 235 | return path 236 | 237 | def get_path_to_system_packages(self): 238 | """Get FreeCAD path from addon preferences.""" 239 | # get the FreeCAD path specified in addon preferences 240 | addon_prefs = self.get_preferences() 241 | path = addon_prefs.filepath_system_packages 242 | print("addon_prefs path_to system_packages", path) 243 | return path 244 | 245 | # def get_path_to(self, target): 246 | # """Get FreeCAD mod path from addon preferences.""" 247 | # # get the FreeCAD path specified in addon preferences 248 | # addon_prefs = self.get_preferences() 249 | # i currently dont know how to do this... 250 | # path = addon_prefs["filepath_" + target] 251 | # print("addon_prefs path to " + target + " ", path) 252 | # return path 253 | 254 | def execute(self, context): 255 | """Call when the user is done using the modal file-select window.""" 256 | path_to_freecad = self.get_path_to_freecad() 257 | # path_to_system_packages = self.get_path_to("system_packages") 258 | path_to_system_packages = self.get_path_to_system_packages() 259 | dir = self.directory 260 | for file in self.files: 261 | filestr = str(file.name) 262 | if filestr.lower().endswith(".fcstd"): 263 | my_importer = import_fcstd.ImportFcstd( 264 | update=self.option_update, 265 | update_only_modified_meshes=self.option_update_only_modified_meshes, 266 | placement=self.option_placement, 267 | scale=self.option_scale, 268 | tessellation=self.option_tessellation, 269 | auto_smooth_use=self.option_auto_smooth_use, 270 | auto_smooth_angle=self.option_auto_smooth_angle, 271 | skiphidden=self.option_skiphidden, 272 | filter_sketch=self.option_filter_sketch, 273 | sharemats=self.option_sharemats, 274 | update_materials=False, 275 | obj_name_prefix=self.option_obj_name_prefix, 276 | obj_name_prefix_with_filename=self.option_prefix_with_filename, 277 | links_as_collectioninstance=self.option_links_as_col, 278 | path_to_freecad=path_to_freecad, 279 | path_to_system_packages=path_to_system_packages, 280 | report=self.report, 281 | ) 282 | return my_importer.import_fcstd(filename=dir + filestr) 283 | return {"FINISHED"} 284 | 285 | 286 | # ============================================================================== 287 | # Register plugin with Blender 288 | # ============================================================================== 289 | 290 | classes = ( 291 | IMPORT_OT_FreeCAD, 292 | IMPORT_OT_FreeCAD_Preferences, 293 | ) 294 | 295 | 296 | def menu_func_import(self, context): 297 | """Needed if you want to add into a dynamic menu.""" 298 | self.layout.operator(IMPORT_OT_FreeCAD.bl_idname, text="FreeCAD (.FCStd)") 299 | 300 | 301 | def register(): 302 | """Register.""" 303 | from bpy.utils import register_class 304 | 305 | for cls in classes: 306 | register_class(cls) 307 | bpy.types.TOPBAR_MT_file_import.append(menu_func_import) 308 | 309 | 310 | def unregister(): 311 | """Unregister.""" 312 | from bpy.utils import unregister_class 313 | 314 | for cls in reversed(classes): 315 | unregister_class(cls) 316 | bpy.types.TOPBAR_MT_file_import.remove(menu_func_import) 317 | 318 | 319 | if __name__ == "__main__": 320 | register() 321 | -------------------------------------------------------------------------------- /blender_helper.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | """Random Helper Functions & Classe for Blender Python Scripting.""" 5 | 6 | # import re 7 | 8 | try: 9 | import bpy 10 | except ModuleNotFoundError as e: 11 | print("Blender 'bpy' not available.", e) 12 | bpy = None 13 | 14 | 15 | # based on 16 | # https://www.geeksforgeeks.org/print-colors-python-terminal/ 17 | # Python program to print 18 | # colored text and background 19 | class colors: 20 | """ 21 | ASCII Color and Control Characters. 22 | 23 | reset all colors with colors.reset; 24 | two sub classes 25 | fg for foreground 26 | bg for background; 27 | use as colors.subclass.colorname: 28 | `colors.fg.red` 29 | `colors.bg.green` 30 | the generics 31 | bold, disable, underline, reverse, strike through, invisible 32 | work with the main class: 33 | `colors.bold` 34 | """ 35 | 36 | reset = '\033[0m' 37 | bold = '\033[01m' 38 | disable = '\033[02m' 39 | underline = '\033[04m' 40 | reverse = '\033[07m' 41 | strikethrough = '\033[09m' 42 | invisible = '\033[08m' 43 | 44 | class fg: 45 | """Forderground Colors.""" 46 | 47 | black = '\033[30m' 48 | red = '\033[31m' 49 | green = '\033[32m' 50 | orange = '\033[33m' 51 | blue = '\033[34m' 52 | purple = '\033[35m' 53 | cyan = '\033[36m' 54 | lightgrey = '\033[37m' 55 | darkgrey = '\033[90m' 56 | lightred = '\033[91m' 57 | lightgreen = '\033[92m' 58 | yellow = '\033[93m' 59 | lightblue = '\033[94m' 60 | pink = '\033[95m' 61 | lightcyan = '\033[96m' 62 | 63 | class bg: 64 | """Background Colors.""" 65 | 66 | black = '\033[40m' 67 | red = '\033[41m' 68 | green = '\033[42m' 69 | orange = '\033[43m' 70 | blue = '\033[44m' 71 | purple = '\033[45m' 72 | cyan = '\033[46m' 73 | lightgrey = '\033[47m' 74 | 75 | @classmethod 76 | def get_flat_list(cls, obj_dict=None): 77 | """Get a flattend list of all control characters in dict.""" 78 | result = [] 79 | if obj_dict is None: 80 | obj_dict = cls.__dict__ 81 | # print("*"*42) 82 | # print("obj_dict", obj_dict) 83 | # print("*"*42) 84 | for attr_name, attr_value in obj_dict.items(): 85 | if not attr_name.startswith("__"): 86 | # if type(attr_value) is str: 87 | # value_str = attr_value.replace("\x1b", "\\x1b") 88 | # else: 89 | # value_str = attr_value 90 | # print( 91 | # "'{}' '{}': {} " 92 | # "".format( 93 | # attr_name, 94 | # type(attr_value), 95 | # value_str, 96 | # ), 97 | # end="" 98 | # ) 99 | if type(attr_value) is str: 100 | # print(" STRING ") 101 | result.append(attr_value) 102 | elif type(attr_value) is type: 103 | # print(" TYPE ") 104 | result.extend( 105 | cls.get_flat_list(attr_value.__dict__) 106 | ) 107 | else: 108 | # print(" UNKNOWN ") 109 | pass 110 | # print("*"*42) 111 | return result 112 | 113 | 114 | def filter_ASCII_controlls(data): 115 | """Remove ASCII controll characters.""" 116 | code_list = colors.get_flat_list() 117 | for el in code_list: 118 | data = data.replace(el, "") 119 | return data 120 | 121 | 122 | def test_filtering(): 123 | """Test for filter_ASCII_controlls.""" 124 | test_string = ( 125 | colors.fg.lightblue + 126 | "Hello " + 127 | colors.fg.green + 128 | "World " + 129 | colors.fg.orange + 130 | ":-)" + 131 | colors.reset 132 | ) 133 | print("test_string", test_string) 134 | test_filtered = filter_ASCII_controlls(test_string) 135 | print("test_filtered", test_filtered) 136 | 137 | 138 | def print_colored(mode, data, pre_line=""): 139 | """Print with coloring similar to blenders info area.""" 140 | printcolor = colors.reset 141 | if mode == {'INFO'}: 142 | printcolor = colors.fg.lightblue 143 | elif mode == {'WARNING'}: 144 | printcolor = colors.fg.orange 145 | elif mode == {'ERROR'}: 146 | printcolor = colors.fg.red 147 | print("{}{}{}{}".format(str(pre_line), printcolor, data, colors.reset)) 148 | 149 | 150 | # https://blender.stackexchange.com/a/142317/16634 151 | def print_blender_console(mode, data, pre_line=""): 152 | """Print to blenders console area.""" 153 | if bpy: 154 | message_type = mode.pop() 155 | if message_type == 'WARNING': 156 | message_type = 'INFO' 157 | elif message_type == 'INFO': 158 | message_type = 'OUTPUT' 159 | else: 160 | message_type = 'INFO' 161 | data = filter_ASCII_controlls(str(data)) 162 | data = filter_ASCII_controlls(str(pre_line)) + data 163 | for window in bpy.context.window_manager.windows: 164 | screen = window.screen 165 | for area in screen.areas: 166 | if area.type == 'CONSOLE': 167 | override = { 168 | 'window': window, 169 | 'screen': screen, 170 | 'area': area 171 | } 172 | bpy.ops.console.scrollback_append( 173 | override, text=data, type=message_type) 174 | 175 | 176 | def print_console(mode, data, pre_line=""): 177 | """Multi-Print to blenders console area and system console.""" 178 | print_colored(mode, data, pre_line=pre_line) 179 | print_blender_console(mode, data, pre_line=pre_line) 180 | 181 | 182 | def print_multi(*, mode, data, pre_line="", report=None): 183 | """Multi-Print to blenders console or info area and system console.""" 184 | # print( 185 | # "print_multi " 186 | # "mode:'{}' pre_line:'{}' data:'{}'" 187 | # "".format(mode, pre_line, data) 188 | # ) 189 | print_colored(mode, data, pre_line) 190 | if report: 191 | data = filter_ASCII_controlls(str(data)) 192 | report(mode, data) 193 | else: 194 | print_blender_console(mode, data, pre_line) 195 | 196 | 197 | # def print_blender_info(mode, data): 198 | # message_type = mode.pop() 199 | # if message_type is 'WARNING': 200 | # message_type = 'ERROR' 201 | # if bpy: 202 | # data = filter_ASCII_controlls(str(data)) 203 | # for window in bpy.context.window_manager.windows: 204 | # screen = window.screen 205 | # for area in screen.areas: 206 | # if area.type == 'CONSOLE': 207 | # override = { 208 | # 'window': window, 209 | # 'screen': screen, 210 | # 'area': area 211 | # } 212 | # bpy.ops.console.scrollback_append( 213 | # override, text=data, type=message_type) 214 | 215 | 216 | def purge_block(data_blocks): 217 | """Remove all unused object blocks.""" 218 | counter = 0 219 | for block in data_blocks: 220 | if block.users == 0: 221 | data_blocks.remove(block) 222 | counter += 1 223 | return counter 224 | 225 | 226 | def purge_all_unused(): 227 | """Remove all unused data blocks.""" 228 | counter = 0 229 | # keep this order. 230 | # as the lower things are contained in the higher ones.. 231 | counter += purge_block(bpy.data.objects) 232 | counter += purge_block(bpy.data.meshes) 233 | counter += purge_block(bpy.data.materials) 234 | counter += purge_block(bpy.data.textures) 235 | counter += purge_block(bpy.data.images) 236 | return counter 237 | -------------------------------------------------------------------------------- /changelog.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | # Change-Log 5 | 6 | - v7.0.0 - 2019-11-?? 7 | - ? 8 | - v6.1.1 - 2019-10-31 9 | - added App::Part handling 10 | - added App::Link handling 11 | - v6.0.0 - 2019-10-02 12 | - forked from gist to github repository 13 | - v5.0.0 - 2019-08-13 14 | - small fixes 15 | - better info messages if things go wrong 16 | - v4.0.0 - 2019-02-07 17 | - API changes 18 | - support of transparency 19 | - v3.0.0 - 2019-02-06 20 | - ported to Blender 2.80 21 | - v2.0.0 - 2018-06-21 22 | - option to turn 23 | - cycles mat on/off 24 | - per-face material support 25 | - use of polygons when possible 26 | - shared materials 27 | - v1.0.0 - 2018-06-12 28 | - initial release - basically working 29 | -------------------------------------------------------------------------------- /dev/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # project specific 3 | temp 4 | temp.py 5 | temp1.py 6 | temp2.py 7 | temp.blend 8 | 9 | # blender 10 | 11 | render 12 | 13 | *.blend1 14 | *.blend2 15 | *.blend3 16 | *.blend4 17 | *.blend5 18 | *.blend6 19 | *.blend7 20 | *.blend8 21 | *.blend9 22 | *.blend10 23 | 24 | 25 | 26 | # For PCBs designed using KiCad: http://www.kicad-pcb.org/ 27 | # Format documentation: http://kicad-pcb.org/help/file-formats/ 28 | 29 | _saved_* 30 | 31 | # Temporary files 32 | *.000 33 | *.bak 34 | *.bck 35 | *.kicad_pcb-bak 36 | *~ 37 | _autosave-* 38 | *.tmp 39 | *-cache.lib 40 | *-rescue.lib 41 | *-save.pro 42 | *-save.kicad_pcb 43 | 44 | # Netlist files (exported from Eeschema) 45 | *.net 46 | 47 | # Autorouter files (exported from Pcbnew) 48 | *.dsn 49 | *.ses 50 | 51 | # Exported BOM files 52 | *.xml 53 | *.csv 54 | 55 | 56 | 57 | 58 | 59 | FreeCAD backup files 60 | *.FCStd1 61 | *.fcstd1 62 | *.FCStd2 63 | *.fcstd2 64 | 65 | # open office / libre office lock files 66 | *.~lock.* 67 | 68 | # OwnCloud conflicts: 69 | *_conflict-* 70 | 71 | # project specific ignores: 72 | # for pcb-pool production files 73 | /pcb-pool* 74 | # for datasheets or other documents that are not allowed to be synced.. 75 | /no_git_sync* 76 | /datasheets* 77 | # for old outdated things 78 | /old* 79 | # for zip backups: 80 | /backup* 81 | 82 | 83 | # Byte-compiled / optimized / DLL files 84 | __pycache__/ 85 | *.py[cod] 86 | *$py.class 87 | 88 | # C extensions 89 | *.so 90 | 91 | # Distribution / packaging 92 | .Python 93 | build/ 94 | develop-eggs/ 95 | dist/ 96 | downloads/ 97 | eggs/ 98 | .eggs/ 99 | lib/ 100 | lib64/ 101 | # parts/ 102 | sdist/ 103 | var/ 104 | wheels/ 105 | *.egg-info/ 106 | .installed.cfg 107 | *.egg 108 | MANIFEST 109 | 110 | # PyInstaller 111 | # Usually these files are written by a python script from a template 112 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 113 | *.manifest 114 | *.spec 115 | 116 | # Installer logs 117 | pip-log.txt 118 | pip-delete-this-directory.txt 119 | 120 | # Unit test / coverage reports 121 | htmlcov/ 122 | .tox/ 123 | .coverage 124 | .coverage.* 125 | .cache 126 | nosetests.xml 127 | coverage.xml 128 | *.cover 129 | .hypothesis/ 130 | .pytest_cache/ 131 | 132 | # Translations 133 | *.mo 134 | *.pot 135 | 136 | # Django stuff: 137 | *.log 138 | local_settings.py 139 | db.sqlite3 140 | 141 | # Flask stuff: 142 | instance/ 143 | .webassets-cache 144 | 145 | # Scrapy stuff: 146 | .scrapy 147 | 148 | # Sphinx documentation 149 | docs/_build/ 150 | 151 | # PyBuilder 152 | target/ 153 | 154 | # Jupyter Notebook 155 | .ipynb_checkpoints 156 | 157 | # pyenv 158 | .python-version 159 | 160 | # celery beat schedule file 161 | celerybeat-schedule 162 | 163 | # SageMath parsed files 164 | *.sage.py 165 | 166 | # Environments 167 | .env 168 | .venv 169 | env/ 170 | venv/ 171 | ENV/ 172 | env.bak/ 173 | venv.bak/ 174 | 175 | # Spyder project settings 176 | .spyderproject 177 | .spyproject 178 | 179 | # Rope project settings 180 | .ropeproject 181 | 182 | # mkdocs documentation 183 | /site 184 | 185 | # mypy 186 | .mypy_cache/ 187 | -------------------------------------------------------------------------------- /dev/forum_post_list_results.md: -------------------------------------------------------------------------------- 1 | hello all, 2 | 3 | i have setup a test at 4 | https://github.com/s-light/io_import_fcstd/blob/master/dev/list_objects.py#L98 5 | 6 | it uses ~4 different concepts to find the 'root' objects. (mostly from this thread..) 7 | you can find them at 8 | https://github.com/s-light/io_import_fcstd/blob/master/freecad_helper.py#L43 9 | 10 | in my FreeCAD test file (freecad_linking_example/assembly.FCStd) i have used a bunch of different objects- 11 | Part (grouping) / Part (Mesh) / one hidden / PartDesign Body / Links & Clones: 12 | [img]https://raw.githubusercontent.com/s-light/io_import_fcstd/master/dev/freecad_linking_example/assembly_tree.png[/img] 13 | 14 | the really funny thing is - the results are different from inside FreeCAD and from external Python console: 15 | 16 | Intern: 17 | [code] 18 | >>> print("FreeCAD version:", FreeCAD.Version()) 19 | FreeCAD version: ['0', '19', '', 'https://code.launchpad.net/~vcs-imports/freecad/trunk', '2019/10/04 07:36:31'] 20 | >>> run_tests(FreeCAD.ActiveDocument) 21 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 22 | get_filtered_objects 29 23 | Name Label TypeId p [Parents] i [InList] o [OutList] g [Group] 24 | obj: Part my_final_assembly App::Part p 0 i 0 o 7 g 6 25 | obj: Link box_panel_part App::Link p 1 i 1 o 3 g 1 26 | obj: Link001 front_panel_part App::Link p 1 i 1 o 1 g 1 27 | obj: Link_i0 Link_i0 App::LinkElement p 2 i 2 o 3 g 1 28 | obj: Link_i1 Link_i1 App::LinkElement p 2 i 2 o 3 g 1 29 | obj: Link002 lamp_part App::Link p 2 i 2 o 1 g 2 30 | obj: Array lamp_Array Part::FeaturePython p 1 i 1 o 3 31 | obj: Body floor_body PartDesign::Body p 1 i 2 o 5 g 3 32 | obj: Sketch floor_Sketch Sketcher::SketchObject p 1 i 2 o 1 33 | obj: Pad floor_Pad PartDesign::Pad p 1 i 3 o 1 34 | obj: Box Cube_Hidden Part::Box p 0 i 0 o 0 35 | obj: Sphere world_sphere Part::Sphere p 0 i 0 o 0 36 | obj: Part001 octagon_part App::Part p 0 i 0 o 2 g 1 37 | obj: Body001 octagon_Body PartDesign::Body p 1 i 1 o 5 g 3 38 | obj: Sketch001 octagon_sketch Sketcher::SketchObject p 1 i 2 o 1 39 | obj: Pad001 octagon_Pad PartDesign::Pad p 1 i 3 o 1 40 | obj: Fillet octagon_Fillet PartDesign::Fillet p 2 i 2 o 2 41 | obj: Fillet001 Fillet001 PartDesign::Fillet p 2 i 2 o 2 42 | obj: Cone blue_cone Part::Cone p 0 i 0 o 0 43 | obj: Box001 cube_colorfull Part::Box p 0 i 1 o 0 44 | obj: Part002 floor_part App::Part p 0 i 0 o 2 g 1 45 | obj: Body002 root_body PartDesign::Body p 0 i 0 o 5 g 3 46 | obj: Sketch002 Sketch002 Sketcher::SketchObject p 1 i 2 o 1 47 | obj: Pad002 Pad002 PartDesign::Pad p 1 i 3 o 1 48 | obj: Fillet002 Fillet002 PartDesign::Fillet p 2 i 2 o 2 49 | obj: Body003 cube_part_clone_Body PartDesign::Body p 0 i 0 o 3 g 1 50 | obj: Clone cube_part_clone PartDesign::FeatureBase p 2 i 2 o 1 51 | obj: Body004 floor_body_clone_Body PartDesign::Body p 0 i 0 o 3 g 1 52 | obj: Clone001 floor_body_clone PartDesign::FeatureBase p 2 i 2 o 1 53 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 54 | get_root_objects 10 55 | Name Label TypeId p [Parents] i [InList] o [OutList] g [Group] 56 | obj: Part my_final_assembly App::Part p 0 i 0 o 7 g 6 57 | obj: Box Cube_Hidden Part::Box p 0 i 0 o 0 58 | obj: Sphere world_sphere Part::Sphere p 0 i 0 o 0 59 | obj: Part001 octagon_part App::Part p 0 i 0 o 2 g 1 60 | obj: Cone blue_cone Part::Cone p 0 i 0 o 0 61 | obj: Box001 cube_colorfull Part::Box p 0 i 1 o 0 62 | obj: Part002 floor_part App::Part p 0 i 0 o 2 g 1 63 | obj: Body002 root_body PartDesign::Body p 0 i 0 o 5 g 3 64 | obj: Body003 cube_part_clone_Body PartDesign::Body p 0 i 0 o 3 g 1 65 | obj: Body004 floor_body_clone_Body PartDesign::Body p 0 i 0 o 3 g 1 66 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 67 | doc.RootObjects 9 68 | Name Label TypeId p [Parents] i [InList] o [OutList] g [Group] 69 | obj: Part my_final_assembly App::Part p 0 i 0 o 7 g 6 70 | obj: Box Cube_Hidden Part::Box p 0 i 0 o 0 71 | obj: Sphere world_sphere Part::Sphere p 0 i 0 o 0 72 | obj: Part001 octagon_part App::Part p 0 i 0 o 2 g 1 73 | obj: Cone blue_cone Part::Cone p 0 i 0 o 0 74 | obj: Part002 floor_part App::Part p 0 i 0 o 2 g 1 75 | obj: Body002 root_body PartDesign::Body p 0 i 0 o 5 g 3 76 | obj: Body003 cube_part_clone_Body PartDesign::Body p 0 i 0 o 3 g 1 77 | obj: Body004 floor_body_clone_Body PartDesign::Body p 0 i 0 o 3 g 1 78 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 79 | get_toplevel_objects 10 80 | Name Label TypeId p [Parents] i [InList] o [OutList] g [Group] 81 | obj: Part my_final_assembly App::Part p 0 i 0 o 7 g 6 82 | obj: Box Cube_Hidden Part::Box p 0 i 0 o 0 83 | obj: Sphere world_sphere Part::Sphere p 0 i 0 o 0 84 | obj: Part001 octagon_part App::Part p 0 i 0 o 2 g 1 85 | obj: Cone blue_cone Part::Cone p 0 i 0 o 0 86 | obj: Box001 cube_colorfull Part::Box p 0 i 1 o 0 87 | obj: Part002 floor_part App::Part p 0 i 0 o 2 g 1 88 | obj: Body002 root_body PartDesign::Body p 0 i 0 o 5 g 3 89 | obj: Body003 cube_part_clone_Body PartDesign::Body p 0 i 0 o 3 g 1 90 | obj: Body004 floor_body_clone_Body PartDesign::Body p 0 i 0 o 3 g 1 91 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 92 | tests done :-) 93 | >>> 94 | >>> 95 | >>> 96 | [/code] 97 | 98 | Extern: 99 | [code] 100 | stefan@stefan-Latitude-E6510:~/mydata/github/blender/io_import_fcstd/dev (master +)$ python3 -i ./list_objects.py 101 | Blender 'bpy' not available. No module named 'bpy' 102 | /home/stefan/mydata/github/blender/io_import_fcstd/dev/list_objects.py 103 | script_dir /home/stefan/mydata/github/blender/io_import_fcstd 104 | base_dir /home/stefan/mydata/github/blender/io_import_fcstd 105 | Configured FreeCAD path: /usr/lib/freecad-daily-python3/lib 106 | Sheet Metal workbench loaded 107 | FreeCAD version: ['0', '19', '', 'https://code.launchpad.net/~vcs-imports/freecad/trunk', '2019/10/04 07:36:31'] 108 | ****************************************** 109 | run import_tests 110 | FreeCAD document: ./dev/freecad_linking_example/assembly.FCStd 111 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 112 | get_filtered_objects 16 113 | Name Label TypeId p [Parents] i [InList] o [OutList] g [Group] 114 | obj: Part my_final_assembly App::Part p 0 i 0 o 7 g 6 115 | obj: Link box_panel_part App::Link p 1 i 1 o 3 g 0 116 | obj: Link001 front_panel_part App::Link p 1 i 1 o 1 g 0 117 | obj: Link_i0 Link_i0 App::LinkElement p 2 i 2 o 3 g 0 118 | obj: Link_i1 Link_i1 App::LinkElement p 2 i 2 o 3 g 0 119 | obj: Link002 lamp_part App::Link p 1 i 2 o 1 g 0 120 | obj: Array lamp_Array Part::FeaturePython p 1 i 1 o 3 121 | obj: Sketch floor_Sketch Sketcher::SketchObject p 0 i 0 o 1 122 | obj: Box Cube_Hidden Part::Box p 0 i 0 o 0 123 | obj: Sphere world_sphere Part::Sphere p 0 i 0 o 0 124 | obj: Part001 octagon_part App::Part p 0 i 0 o 1 g 0 125 | obj: Sketch001 octagon_sketch Sketcher::SketchObject p 0 i 0 o 1 126 | obj: Cone blue_cone Part::Cone p 0 i 0 o 0 127 | obj: Box001 cube_colorfull Part::Box p 0 i 0 o 0 128 | obj: Part002 floor_part App::Part p 0 i 0 o 1 g 0 129 | obj: Sketch002 Sketch002 Sketcher::SketchObject p 0 i 0 o 1 130 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 131 | get_root_objects 7 132 | Name Label TypeId p [Parents] i [InList] o [OutList] g [Group] 133 | obj: Part my_final_assembly App::Part p 0 i 0 o 7 g 6 134 | obj: Box Cube_Hidden Part::Box p 0 i 0 o 0 135 | obj: Sphere world_sphere Part::Sphere p 0 i 0 o 0 136 | obj: Part001 octagon_part App::Part p 0 i 0 o 1 g 0 137 | obj: Cone blue_cone Part::Cone p 0 i 0 o 0 138 | obj: Box001 cube_colorfull Part::Box p 0 i 0 o 0 139 | obj: Part002 floor_part App::Part p 0 i 0 o 1 g 0 140 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 141 | doc.RootObjects 15 142 | Name Label TypeId p [Parents] i [InList] o [OutList] g [Group] 143 | obj: Part my_final_assembly App::Part p 0 i 0 o 7 g 6 144 | obj: Origin001 Origin001 App::Origin p 0 i 0 o 6 145 | obj: Sketch floor_Sketch Sketcher::SketchObject p 0 i 0 o 1 146 | obj: Box Cube_Hidden Part::Box p 0 i 0 o 0 147 | obj: Sphere world_sphere Part::Sphere p 0 i 0 o 0 148 | obj: Part001 octagon_part App::Part p 0 i 0 o 1 g 0 149 | obj: Origin003 Origin003 App::Origin p 0 i 0 o 6 150 | obj: Sketch001 octagon_sketch Sketcher::SketchObject p 0 i 0 o 1 151 | obj: Cone blue_cone Part::Cone p 0 i 0 o 0 152 | obj: Box001 cube_colorfull Part::Box p 0 i 0 o 0 153 | obj: Part002 floor_part App::Part p 0 i 0 o 1 g 0 154 | obj: Origin005 Origin005 App::Origin p 0 i 0 o 6 155 | obj: Sketch002 Sketch002 Sketcher::SketchObject p 0 i 0 o 1 156 | obj: Origin006 Origin006 App::Origin p 0 i 0 o 6 157 | obj: Origin007 Origin007 App::Origin p 0 i 0 o 6 158 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 159 | get_toplevel_objects 15 160 | Name Label TypeId p [Parents] i [InList] o [OutList] g [Group] 161 | obj: Part my_final_assembly App::Part p 0 i 0 o 7 g 6 162 | obj: Origin001 Origin001 App::Origin p 0 i 0 o 6 163 | obj: Sketch floor_Sketch Sketcher::SketchObject p 0 i 0 o 1 164 | obj: Box Cube_Hidden Part::Box p 0 i 0 o 0 165 | obj: Sphere world_sphere Part::Sphere p 0 i 0 o 0 166 | obj: Part001 octagon_part App::Part p 0 i 0 o 1 g 0 167 | obj: Origin003 Origin003 App::Origin p 0 i 0 o 6 168 | obj: Sketch001 octagon_sketch Sketcher::SketchObject p 0 i 0 o 1 169 | obj: Cone blue_cone Part::Cone p 0 i 0 o 0 170 | obj: Box001 cube_colorfull Part::Box p 0 i 0 o 0 171 | obj: Part002 floor_part App::Part p 0 i 0 o 1 g 0 172 | obj: Origin005 Origin005 App::Origin p 0 i 0 o 6 173 | obj: Sketch002 Sketch002 Sketcher::SketchObject p 0 i 0 o 1 174 | obj: Origin006 Origin006 App::Origin p 0 i 0 o 6 175 | obj: Origin007 Origin007 App::Origin p 0 i 0 o 6 176 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 177 | tests done :-) 178 | ****************************************** 179 | >>> 180 | 181 | [/code] 182 | 183 | interestingly i can't seem to access the 'Body' objects from the commandline version: 184 | intern 185 | [code] 186 | >>> doc = FreeCAD.ActiveDocument 187 | >>> doc.FileName 188 | '/home/stefan/mydata/github/blender/io_import_fcstd/dev/freecad_linking_example/assembly.FCStd' 189 | >>> obj = doc.getObjectsByLabel("octagon_Body")[0] 190 | >>> obj 191 | 192 | >>> obj.Label 193 | 'octagon_Body' 194 | >>> [/code] 195 | 196 | extern 197 | [code] 198 | >>> doc.FileName 199 | './freecad_linking_example/assembly.FCStd' 200 | >>> obj = doc.getObjectsByLabel("octagon_Body")[0] 201 | Traceback (most recent call last): 202 | File "", line 1, in 203 | IndexError: list index out of range 204 | >>> doc.getObjectsByLabel("octagon_Body") 205 | [] 206 | >>> 207 | [/code] 208 | 209 | is this expected?? 210 | 211 | sunny greetings 212 | stefan 213 | 214 | EDIT: rework output so its readable in the forum. 215 | -------------------------------------------------------------------------------- /dev/freecad_linking_example/assembly.FCStd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s-light/io_import_fcstd/ace3fa082fd0b60721a8338680889fafb7c64024/dev/freecad_linking_example/assembly.FCStd -------------------------------------------------------------------------------- /dev/freecad_linking_example/assembly.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s-light/io_import_fcstd/ace3fa082fd0b60721a8338680889fafb7c64024/dev/freecad_linking_example/assembly.png -------------------------------------------------------------------------------- /dev/freecad_linking_example/assembly_export.FCStd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s-light/io_import_fcstd/ace3fa082fd0b60721a8338680889fafb7c64024/dev/freecad_linking_example/assembly_export.FCStd -------------------------------------------------------------------------------- /dev/freecad_linking_example/assembly_export.blend: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s-light/io_import_fcstd/ace3fa082fd0b60721a8338680889fafb7c64024/dev/freecad_linking_example/assembly_export.blend -------------------------------------------------------------------------------- /dev/freecad_linking_example/assembly_tree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s-light/io_import_fcstd/ace3fa082fd0b60721a8338680889fafb7c64024/dev/freecad_linking_example/assembly_tree.png -------------------------------------------------------------------------------- /dev/freecad_linking_example/base_dimensions.FCStd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s-light/io_import_fcstd/ace3fa082fd0b60721a8338680889fafb7c64024/dev/freecad_linking_example/base_dimensions.FCStd -------------------------------------------------------------------------------- /dev/freecad_linking_example/box_panel.FCStd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s-light/io_import_fcstd/ace3fa082fd0b60721a8338680889fafb7c64024/dev/freecad_linking_example/box_panel.FCStd -------------------------------------------------------------------------------- /dev/freecad_linking_example/front_panel.FCStd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s-light/io_import_fcstd/ace3fa082fd0b60721a8338680889fafb7c64024/dev/freecad_linking_example/front_panel.FCStd -------------------------------------------------------------------------------- /dev/freecad_linking_example/lamp.FCStd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s-light/io_import_fcstd/ace3fa082fd0b60721a8338680889fafb7c64024/dev/freecad_linking_example/lamp.FCStd -------------------------------------------------------------------------------- /dev/freecad_test_ArchWB/ArchTest.FCStd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s-light/io_import_fcstd/ace3fa082fd0b60721a8338680889fafb7c64024/dev/freecad_test_ArchWB/ArchTest.FCStd -------------------------------------------------------------------------------- /dev/freecad_test_ArchWB/ArchTest.blend: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s-light/io_import_fcstd/ace3fa082fd0b60721a8338680889fafb7c64024/dev/freecad_test_ArchWB/ArchTest.blend -------------------------------------------------------------------------------- /dev/freecad_test_ArchWB/building.FCStd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s-light/io_import_fcstd/ace3fa082fd0b60721a8338680889fafb7c64024/dev/freecad_test_ArchWB/building.FCStd -------------------------------------------------------------------------------- /dev/freecad_test_ArchWB/building.blend: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s-light/io_import_fcstd/ace3fa082fd0b60721a8338680889fafb7c64024/dev/freecad_test_ArchWB/building.blend -------------------------------------------------------------------------------- /dev/freecad_test_ArchWB/cube_with_color_faces.FCStd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s-light/io_import_fcstd/ace3fa082fd0b60721a8338680889fafb7c64024/dev/freecad_test_ArchWB/cube_with_color_faces.FCStd -------------------------------------------------------------------------------- /dev/freecad_test_ArchWB/simple_wall.FCStd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s-light/io_import_fcstd/ace3fa082fd0b60721a8338680889fafb7c64024/dev/freecad_test_ArchWB/simple_wall.FCStd -------------------------------------------------------------------------------- /dev/freecad_test_ArchWB/simple_wall_with_door.FCStd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s-light/io_import_fcstd/ace3fa082fd0b60721a8338680889fafb7c64024/dev/freecad_test_ArchWB/simple_wall_with_door.FCStd -------------------------------------------------------------------------------- /dev/freecad_test_ArchWB/simple_wall_with_door.blend: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s-light/io_import_fcstd/ace3fa082fd0b60721a8338680889fafb7c64024/dev/freecad_test_ArchWB/simple_wall_with_door.blend -------------------------------------------------------------------------------- /dev/freecad_test_ArchWB/simple_window.FCStd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s-light/io_import_fcstd/ace3fa082fd0b60721a8338680889fafb7c64024/dev/freecad_test_ArchWB/simple_window.FCStd -------------------------------------------------------------------------------- /dev/freecad_test_ArchWB/simple_window.blend: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s-light/io_import_fcstd/ace3fa082fd0b60721a8338680889fafb7c64024/dev/freecad_test_ArchWB/simple_window.blend -------------------------------------------------------------------------------- /dev/freecad_test_ArchWB/test_MultiMaterial.FCStd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s-light/io_import_fcstd/ace3fa082fd0b60721a8338680889fafb7c64024/dev/freecad_test_ArchWB/test_MultiMaterial.FCStd -------------------------------------------------------------------------------- /dev/freecad_test_DraftLinkedArray/Test_DraftLinkedArray.FCStd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s-light/io_import_fcstd/ace3fa082fd0b60721a8338680889fafb7c64024/dev/freecad_test_DraftLinkedArray/Test_DraftLinkedArray.FCStd -------------------------------------------------------------------------------- /dev/freecad_test_DraftLinkedArray/Test_DraftLinkedArray.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s-light/io_import_fcstd/ace3fa082fd0b60721a8338680889fafb7c64024/dev/freecad_test_DraftLinkedArray/Test_DraftLinkedArray.png -------------------------------------------------------------------------------- /dev/freecad_test_DraftLinkedArray/Test_DraftLinkedArray_ObjectTree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s-light/io_import_fcstd/ace3fa082fd0b60721a8338680889fafb7c64024/dev/freecad_test_DraftLinkedArray/Test_DraftLinkedArray_ObjectTree.png -------------------------------------------------------------------------------- /dev/freecad_test_DraftLinkedArray/justopen.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | """ 5 | Just open the file. 6 | 7 | use from commandline or copy und paste content into running python3 instance 8 | """ 9 | 10 | import sys 11 | 12 | path_to_freecad = "/usr/lib/freecad-daily-python3/lib/" 13 | try: 14 | sys.path.append(path_to_freecad) 15 | import FreeCAD 16 | print("FreeCAD version:", FreeCAD.Version()) 17 | except ModuleNotFoundError as e: 18 | print("FreeCAD import failed.", e) 19 | 20 | 21 | def open_file(filename): 22 | docname = "" 23 | try: 24 | print("FreeCAD document:", filename) 25 | print("open file..") 26 | doc = FreeCAD.open(filename) 27 | print("file is opened.") 28 | docname = doc.Name 29 | if not doc: 30 | print("Unable to open the given FreeCAD file") 31 | else: 32 | print("file contains {} objects.".format(len(doc.Objects))) 33 | except Exception as e: 34 | raise e 35 | finally: 36 | if docname: 37 | print("close file..") 38 | FreeCAD.closeDocument(docname) 39 | print("file is closed.") 40 | 41 | 42 | # ****************************************** 43 | if __name__ == '__main__': 44 | "Main Tests." 45 | print("*"*42) 46 | filename = "./Test_DraftLinkedArray.FCStd" 47 | open_file(filename) 48 | print("*"*42) 49 | -------------------------------------------------------------------------------- /dev/freecad_test_DraftLinkedArray/justopen_copy_paste.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | """ 5 | Just opens a file. 6 | 7 | copy und paste content into running python3 instance 8 | """ 9 | 10 | import sys 11 | sys.path.append("/usr/lib/freecad-daily-python3/lib/") 12 | import FreeCAD 13 | 14 | print("FreeCAD version:", FreeCAD.Version()) 15 | print("*"*42) 16 | doc = FreeCAD.open("./Test_DraftLinkedArray.FCStd") 17 | 18 | 19 | print("file contains {} objects.".format(len(doc.Objects))) 20 | FreeCAD.closeDocument(doc.Name) 21 | print("file closed.") 22 | -------------------------------------------------------------------------------- /dev/freecad_test_DraftLinkedArray_Visibility/DraftLinkedArray_Visibility.FCStd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s-light/io_import_fcstd/ace3fa082fd0b60721a8338680889fafb7c64024/dev/freecad_test_DraftLinkedArray_Visibility/DraftLinkedArray_Visibility.FCStd -------------------------------------------------------------------------------- /dev/freecad_test_DraftLinkedArray_Visibility/DraftLinkedArray_Visibility2.FCStd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s-light/io_import_fcstd/ace3fa082fd0b60721a8338680889fafb7c64024/dev/freecad_test_DraftLinkedArray_Visibility/DraftLinkedArray_Visibility2.FCStd -------------------------------------------------------------------------------- /dev/freecad_test_MyLittleWorld/MyLittleWorld.FCStd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s-light/io_import_fcstd/ace3fa082fd0b60721a8338680889fafb7c64024/dev/freecad_test_MyLittleWorld/MyLittleWorld.FCStd -------------------------------------------------------------------------------- /dev/freecad_test_MyLittleWorld/MyLittleWorld.blend: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s-light/io_import_fcstd/ace3fa082fd0b60721a8338680889fafb7c64024/dev/freecad_test_MyLittleWorld/MyLittleWorld.blend -------------------------------------------------------------------------------- /dev/freecad_test_MyLittleWorld/MyLittleWorld.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s-light/io_import_fcstd/ace3fa082fd0b60721a8338680889fafb7c64024/dev/freecad_test_MyLittleWorld/MyLittleWorld.png -------------------------------------------------------------------------------- /dev/freecad_test_MyLittleWorld/MyLittleWorld_ObjectTree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s-light/io_import_fcstd/ace3fa082fd0b60721a8338680889fafb7c64024/dev/freecad_test_MyLittleWorld/MyLittleWorld_ObjectTree.png -------------------------------------------------------------------------------- /dev/freecad_test_MyLittleWorld/list_objects.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | """ 5 | List Objects. 6 | 7 | Stand-alone 8 | """ 9 | 10 | import sys 11 | import os 12 | 13 | 14 | path_to_freecad = "/usr/lib/freecad-daily-python3/lib/FreeCAD.so" 15 | 16 | 17 | def append_freecad_path(): 18 | """Append the FreeCAD path.""" 19 | global path_to_freecad 20 | if os.path.exists(path_to_freecad): 21 | if os.path.isfile(path_to_freecad): 22 | path_to_freecad = os.path.dirname(path_to_freecad) 23 | print("Configured FreeCAD path:", path_to_freecad) 24 | if path_to_freecad not in sys.path: 25 | sys.path.append(path_to_freecad) 26 | else: 27 | print("FreeCAD path is not correct.") 28 | 29 | 30 | try: 31 | try: 32 | import FreeCAD 33 | except ModuleNotFoundError: 34 | append_freecad_path() 35 | import FreeCAD # noqa 36 | print("FreeCAD version:", FreeCAD.Version()) 37 | except ModuleNotFoundError as e: 38 | print("FreeCAD import failed.", e) 39 | 40 | 41 | # ****************************************** 42 | 43 | def print_obj_header( 44 | pre_line="", 45 | show_lists=False, 46 | show_list_details=False, 47 | ): 48 | """Print header for objects list.""" 49 | print( 50 | pre_line + 51 | "{:<15} {:<25} {:<25}" 52 | "".format("Label", "Name", "TypeId"), 53 | end='' 54 | ) 55 | if show_lists: 56 | print( 57 | "{:>2} {:>2} {:>2} {:>2}" 58 | "".format( 59 | "P", 60 | "I", 61 | "O", 62 | "G", 63 | ), 64 | end='' 65 | ) 66 | if show_list_details: 67 | print( 68 | ( 69 | " {:<10}" * 4 70 | ).format( 71 | '[Parents]', 72 | '[InList]', 73 | '[OutList]', 74 | '[Group]' 75 | ), 76 | end='' 77 | ) 78 | print() 79 | 80 | 81 | def print_obj( 82 | obj, 83 | pre_line="", 84 | show_lists=False, 85 | show_list_details=False, 86 | end="\n", 87 | ): 88 | """Print object nicely formated.""" 89 | print( 90 | pre_line + 91 | "{:<25} {:<15} {:<25}" 92 | "".format(obj.Label, obj.Name, obj.TypeId), 93 | end='' 94 | ) 95 | if show_lists: 96 | group_count = '_' 97 | if hasattr(obj, 'Group'): 98 | group_count = len(obj.Group) 99 | print( 100 | "{:>2} {:>2} {:>2} {:>2}" 101 | "".format( 102 | len(obj.Parents), 103 | len(obj.InList), 104 | len(obj.OutList), 105 | group_count 106 | ), 107 | end='' 108 | ) 109 | if show_list_details: 110 | group = None 111 | if hasattr(obj, 'Group'): 112 | group = obj.Group 113 | print( 114 | ( 115 | " {:<10}" * 4 116 | ).format( 117 | str(obj.Parents), 118 | str(obj.InList), 119 | str(obj.OutList), 120 | str(group) 121 | ), 122 | end='' 123 | ) 124 | print("", end=end) 125 | 126 | 127 | def print_objects( 128 | objects, 129 | pre_line="", 130 | pre_list_entry="* ", 131 | show_lists=False, 132 | show_list_details=False, 133 | ): 134 | """Print objects list.""" 135 | pre_list_entry_space = " "*len(pre_list_entry) 136 | print_obj_header( 137 | pre_line=pre_line + pre_list_entry_space, 138 | show_lists=show_lists, 139 | show_list_details=show_list_details, 140 | ) 141 | for obj in objects: 142 | print_obj( 143 | obj, 144 | pre_line=pre_line + pre_list_entry, 145 | show_lists=show_lists, 146 | show_list_details=show_list_details, 147 | ) 148 | 149 | 150 | def print_obj_with_label(doc, label): 151 | """Print object with given label.""" 152 | obj = doc.getObjectsByLabel(label) 153 | # print(obj) 154 | if len(obj) > 0: 155 | obj = obj[0] 156 | print_obj(obj) 157 | else: 158 | print("object with label '{}' not found.".format(label)) 159 | 160 | 161 | # ****************************************** 162 | # 163 | # Main experimetns 164 | # 165 | # ****************************************** 166 | 167 | doc = FreeCAD.open( 168 | "./MyLittleWorld.FCStd" 169 | ) 170 | docname = doc.Name 171 | 172 | # ****************************************** 173 | print("~"*42) 174 | objects = doc.Objects 175 | print("doc.Objects", len(objects)) 176 | print_objects(objects) 177 | print("~"*42) 178 | 179 | print_obj_with_label(doc, "World_Body") 180 | print_obj_with_label(doc, "Sun_Sphere") 181 | print_obj_with_label(doc, "Seagull_Body") 182 | 183 | print("~"*42) 184 | print("tests done :-)") 185 | 186 | FreeCAD.closeDocument(docname) 187 | -------------------------------------------------------------------------------- /dev/freecad_test_MyLittleWorld/list_objects_copypaste.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | """ 5 | List Objects. 6 | 7 | Copy&Paste this complete script into the FreeCAD Python console! 8 | """ 9 | 10 | import FreeCAD 11 | 12 | 13 | def print_obj_header( 14 | pre_line="", 15 | show_lists=False, 16 | show_list_details=False, 17 | ): 18 | """Print header for objects list.""" 19 | print( 20 | pre_line + 21 | "{:<15} {:<25} {:<25}" 22 | "".format("Label", "Name", "TypeId"), 23 | end='' 24 | ) 25 | if show_lists: 26 | print( 27 | "{:>2} {:>2} {:>2} {:>2}" 28 | "".format( 29 | "P", 30 | "I", 31 | "O", 32 | "G", 33 | ), 34 | end='' 35 | ) 36 | if show_list_details: 37 | print( 38 | ( 39 | " {:<10}" * 4 40 | ).format( 41 | '[Parents]', 42 | '[InList]', 43 | '[OutList]', 44 | '[Group]' 45 | ), 46 | end='' 47 | ) 48 | print() 49 | 50 | 51 | def print_obj( 52 | obj, 53 | pre_line="", 54 | show_lists=False, 55 | show_list_details=False, 56 | end="\n", 57 | ): 58 | """Print object nicely formated.""" 59 | print( 60 | pre_line + 61 | "{:<25} {:<15} {:<25}" 62 | "".format(obj.Label, obj.Name, obj.TypeId), 63 | end='' 64 | ) 65 | if show_lists: 66 | group_count = '_' 67 | if hasattr(obj, 'Group'): 68 | group_count = len(obj.Group) 69 | print( 70 | "{:>2} {:>2} {:>2} {:>2}" 71 | "".format( 72 | len(obj.Parents), 73 | len(obj.InList), 74 | len(obj.OutList), 75 | group_count 76 | ), 77 | end='' 78 | ) 79 | if show_list_details: 80 | group = None 81 | if hasattr(obj, 'Group'): 82 | group = obj.Group 83 | print( 84 | ( 85 | " {:<10}" * 4 86 | ).format( 87 | str(obj.Parents), 88 | str(obj.InList), 89 | str(obj.OutList), 90 | str(group) 91 | ), 92 | end='' 93 | ) 94 | print("", end=end) 95 | 96 | 97 | def print_objects( 98 | objects, 99 | pre_line="", 100 | pre_list_entry="* ", 101 | show_lists=False, 102 | show_list_details=False, 103 | ): 104 | """Print objects list.""" 105 | pre_list_entry_space = " "*len(pre_list_entry) 106 | print_obj_header( 107 | pre_line=pre_line + pre_list_entry_space, 108 | show_lists=show_lists, 109 | show_list_details=show_list_details, 110 | ) 111 | for obj in objects: 112 | print_obj( 113 | obj, 114 | pre_line=pre_line + pre_list_entry, 115 | show_lists=show_lists, 116 | show_list_details=show_list_details, 117 | ) 118 | 119 | 120 | def print_obj_with_label(doc, label): 121 | """Print object with given label.""" 122 | obj = doc.getObjectsByLabel(label) 123 | # print(obj) 124 | if len(obj) > 0: 125 | obj = obj[0] 126 | print_obj(obj) 127 | else: 128 | print("object with label '{}' not found.".format(label)) 129 | 130 | 131 | # ****************************************** 132 | # 133 | # Main experimetns 134 | # 135 | # ****************************************** 136 | 137 | doc = FreeCAD.ActiveDocument 138 | docname = doc.Name 139 | 140 | # ****************************************** 141 | objects = doc.Objects 142 | print("doc.Objects", len(objects)) 143 | print_objects(objects) 144 | 145 | # ****************************************** 146 | 147 | print_obj_with_label(doc, "World_Body") 148 | print_obj_with_label(doc, "Sun_Sphere") 149 | print_obj_with_label(doc, "Seagull_Body") 150 | 151 | # ****************************************** 152 | print("tests done :-)") 153 | -------------------------------------------------------------------------------- /dev/freecad_test_ParentChildPositions/TestParentChildPositions.FCStd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s-light/io_import_fcstd/ace3fa082fd0b60721a8338680889fafb7c64024/dev/freecad_test_ParentChildPositions/TestParentChildPositions.FCStd -------------------------------------------------------------------------------- /dev/freecad_test_ParentChildPositions/TestParentChildPositions.blend: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s-light/io_import_fcstd/ace3fa082fd0b60721a8338680889fafb7c64024/dev/freecad_test_ParentChildPositions/TestParentChildPositions.blend -------------------------------------------------------------------------------- /dev/freecad_test_ParentChildPositions/TestParentChildPositions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s-light/io_import_fcstd/ace3fa082fd0b60721a8338680889fafb7c64024/dev/freecad_test_ParentChildPositions/TestParentChildPositions.png -------------------------------------------------------------------------------- /dev/freecad_test_ParentChildPositions/TestParentChildPositions_ObjectTree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s-light/io_import_fcstd/ace3fa082fd0b60721a8338680889fafb7c64024/dev/freecad_test_ParentChildPositions/TestParentChildPositions_ObjectTree.png -------------------------------------------------------------------------------- /dev/freecad_test_body_objects/BodyTest.FCStd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s-light/io_import_fcstd/ace3fa082fd0b60721a8338680889fafb7c64024/dev/freecad_test_body_objects/BodyTest.FCStd -------------------------------------------------------------------------------- /dev/freecad_test_body_objects/BodyTest.blend: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s-light/io_import_fcstd/ace3fa082fd0b60721a8338680889fafb7c64024/dev/freecad_test_body_objects/BodyTest.blend -------------------------------------------------------------------------------- /dev/freecad_test_body_objects/BodyTest.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s-light/io_import_fcstd/ace3fa082fd0b60721a8338680889fafb7c64024/dev/freecad_test_body_objects/BodyTest.png -------------------------------------------------------------------------------- /dev/freecad_test_body_objects/BodyTest_Minimal.FCStd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s-light/io_import_fcstd/ace3fa082fd0b60721a8338680889fafb7c64024/dev/freecad_test_body_objects/BodyTest_Minimal.FCStd -------------------------------------------------------------------------------- /dev/freecad_test_body_objects/BodyTest_Minimal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s-light/io_import_fcstd/ace3fa082fd0b60721a8338680889fafb7c64024/dev/freecad_test_body_objects/BodyTest_Minimal.png -------------------------------------------------------------------------------- /dev/freecad_test_body_objects/BodyTest__ObjectTree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s-light/io_import_fcstd/ace3fa082fd0b60721a8338680889fafb7c64024/dev/freecad_test_body_objects/BodyTest__ObjectTree.png -------------------------------------------------------------------------------- /dev/freecad_test_body_objects/list_objects.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | """ 5 | List Objects. 6 | 7 | Stand-alone 8 | """ 9 | 10 | import sys 11 | import os 12 | 13 | 14 | path_to_freecad = "/usr/lib/freecad-daily-python3/lib/FreeCAD.so" 15 | 16 | 17 | def append_freecad_path(): 18 | """Append the FreeCAD path.""" 19 | global path_to_freecad 20 | if os.path.exists(path_to_freecad): 21 | if os.path.isfile(path_to_freecad): 22 | path_to_freecad = os.path.dirname(path_to_freecad) 23 | print("Configured FreeCAD path:", path_to_freecad) 24 | if path_to_freecad not in sys.path: 25 | sys.path.append(path_to_freecad) 26 | else: 27 | print("FreeCAD path is not correct.") 28 | 29 | 30 | try: 31 | try: 32 | import FreeCAD 33 | except ModuleNotFoundError: 34 | append_freecad_path() 35 | import FreeCAD # noqa 36 | print("FreeCAD version:", FreeCAD.Version()) 37 | except ModuleNotFoundError as e: 38 | print("FreeCAD import failed.", e) 39 | 40 | 41 | # ****************************************** 42 | 43 | def print_obj_header( 44 | pre_line="", 45 | show_lists=False, 46 | show_list_details=False, 47 | ): 48 | """Print header for objects list.""" 49 | print( 50 | pre_line + 51 | "{:<15} {:<25} {:<25}" 52 | "".format("Label", "Name", "TypeId"), 53 | end='' 54 | ) 55 | if show_lists: 56 | print( 57 | "{:>2} {:>2} {:>2} {:>2}" 58 | "".format( 59 | "P", 60 | "I", 61 | "O", 62 | "G", 63 | ), 64 | end='' 65 | ) 66 | if show_list_details: 67 | print( 68 | ( 69 | " {:<10}" * 4 70 | ).format( 71 | '[Parents]', 72 | '[InList]', 73 | '[OutList]', 74 | '[Group]' 75 | ), 76 | end='' 77 | ) 78 | print() 79 | 80 | 81 | def print_obj( 82 | obj, 83 | pre_line="", 84 | show_lists=False, 85 | show_list_details=False, 86 | end="\n", 87 | ): 88 | """Print object nicely formated.""" 89 | print( 90 | pre_line + 91 | "{:<25} {:<15} {:<25}" 92 | "".format(obj.Label, obj.Name, obj.TypeId), 93 | end='' 94 | ) 95 | if show_lists: 96 | group_count = '_' 97 | if hasattr(obj, 'Group'): 98 | group_count = len(obj.Group) 99 | print( 100 | "{:>2} {:>2} {:>2} {:>2}" 101 | "".format( 102 | len(obj.Parents), 103 | len(obj.InList), 104 | len(obj.OutList), 105 | group_count 106 | ), 107 | end='' 108 | ) 109 | if show_list_details: 110 | group = None 111 | if hasattr(obj, 'Group'): 112 | group = obj.Group 113 | print( 114 | ( 115 | " {:<10}" * 4 116 | ).format( 117 | str(obj.Parents), 118 | str(obj.InList), 119 | str(obj.OutList), 120 | str(group) 121 | ), 122 | end='' 123 | ) 124 | print("", end=end) 125 | 126 | 127 | def print_objects( 128 | objects, 129 | pre_line="", 130 | pre_list_entry="* ", 131 | show_lists=False, 132 | show_list_details=False, 133 | ): 134 | """Print objects list.""" 135 | pre_list_entry_space = " "*len(pre_list_entry) 136 | print_obj_header( 137 | pre_line=pre_line + pre_list_entry_space, 138 | show_lists=show_lists, 139 | show_list_details=show_list_details, 140 | ) 141 | for obj in objects: 142 | print_obj( 143 | obj, 144 | pre_line=pre_line + pre_list_entry, 145 | show_lists=show_lists, 146 | show_list_details=show_list_details, 147 | ) 148 | 149 | 150 | def print_obj_with_label(doc, label): 151 | """Print object with given label.""" 152 | obj = doc.getObjectsByLabel(label) 153 | # print(obj) 154 | if len(obj) > 0: 155 | obj = obj[0] 156 | print_obj(obj) 157 | else: 158 | print("object with label '{}' not found.".format(label)) 159 | 160 | 161 | # ****************************************** 162 | # 163 | # Main experimetns 164 | # 165 | # ****************************************** 166 | 167 | doc = FreeCAD.open( 168 | "./BodyTest.FCStd" 169 | ) 170 | docname = doc.Name 171 | 172 | # ****************************************** 173 | print("~"*42) 174 | objects = doc.Objects 175 | print("doc.Objects", len(objects)) 176 | print_objects(objects) 177 | print("~"*42) 178 | 179 | print_obj_with_label(doc, "BodyTest_Part") 180 | print_obj_with_label(doc, "Body_Hex") 181 | print_obj_with_label(doc, "Sphere_Sun") 182 | 183 | print("~"*42) 184 | print("tests done :-)") 185 | 186 | FreeCAD.closeDocument(docname) 187 | -------------------------------------------------------------------------------- /dev/freecad_test_body_objects/list_objects__minimal.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | """ 5 | List Objects. 6 | 7 | Stand-alone - run from commandline or copy & paste into python interpreter 8 | """ 9 | 10 | import sys 11 | path_to_freecad = "/usr/lib/freecad-daily-python3/lib/" 12 | sys.path.append(path_to_freecad) 13 | 14 | import FreeCAD 15 | print("FreeCAD version:", FreeCAD.Version()) 16 | 17 | doc = FreeCAD.open("./BodyTest_Minimal.FCStd") 18 | docname = doc.Name 19 | objects = FreeCAD.ActiveDocument.Objects 20 | 21 | print("doc.Objects", len(objects)) 22 | 23 | for o in objects: 24 | print(o, o.Name) 25 | 26 | FreeCAD.closeDocument(docname) 27 | -------------------------------------------------------------------------------- /dev/freecad_test_body_objects/list_objects_copypaste.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | """ 5 | List Objects. 6 | 7 | Copy&Paste this complete script into the FreeCAD Python console! 8 | """ 9 | 10 | import FreeCAD 11 | 12 | 13 | def print_obj_header( 14 | pre_line="", 15 | show_lists=False, 16 | show_list_details=False, 17 | ): 18 | """Print header for objects list.""" 19 | print( 20 | pre_line + 21 | "{:<15} {:<25} {:<25}" 22 | "".format("Label", "Name", "TypeId"), 23 | end='' 24 | ) 25 | if show_lists: 26 | print( 27 | "{:>2} {:>2} {:>2} {:>2}" 28 | "".format( 29 | "P", 30 | "I", 31 | "O", 32 | "G", 33 | ), 34 | end='' 35 | ) 36 | if show_list_details: 37 | print( 38 | ( 39 | " {:<10}" * 4 40 | ).format( 41 | '[Parents]', 42 | '[InList]', 43 | '[OutList]', 44 | '[Group]' 45 | ), 46 | end='' 47 | ) 48 | print() 49 | 50 | 51 | def print_obj( 52 | obj, 53 | pre_line="", 54 | show_lists=False, 55 | show_list_details=False, 56 | end="\n", 57 | ): 58 | """Print object nicely formated.""" 59 | print( 60 | pre_line + 61 | "{:<25} {:<15} {:<25}" 62 | "".format(obj.Label, obj.Name, obj.TypeId), 63 | end='' 64 | ) 65 | if show_lists: 66 | group_count = '_' 67 | if hasattr(obj, 'Group'): 68 | group_count = len(obj.Group) 69 | print( 70 | "{:>2} {:>2} {:>2} {:>2}" 71 | "".format( 72 | len(obj.Parents), 73 | len(obj.InList), 74 | len(obj.OutList), 75 | group_count 76 | ), 77 | end='' 78 | ) 79 | if show_list_details: 80 | group = None 81 | if hasattr(obj, 'Group'): 82 | group = obj.Group 83 | print( 84 | ( 85 | " {:<10}" * 4 86 | ).format( 87 | str(obj.Parents), 88 | str(obj.InList), 89 | str(obj.OutList), 90 | str(group) 91 | ), 92 | end='' 93 | ) 94 | print("", end=end) 95 | 96 | 97 | def print_objects( 98 | objects, 99 | pre_line="", 100 | pre_list_entry="* ", 101 | show_lists=False, 102 | show_list_details=False, 103 | ): 104 | """Print objects list.""" 105 | pre_list_entry_space = " "*len(pre_list_entry) 106 | print_obj_header( 107 | pre_line=pre_line + pre_list_entry_space, 108 | show_lists=show_lists, 109 | show_list_details=show_list_details, 110 | ) 111 | for obj in objects: 112 | print_obj( 113 | obj, 114 | pre_line=pre_line + pre_list_entry, 115 | show_lists=show_lists, 116 | show_list_details=show_list_details, 117 | ) 118 | 119 | 120 | def print_obj_with_label(doc, label): 121 | """Print object with given label.""" 122 | obj = doc.getObjectsByLabel(label) 123 | # print(obj) 124 | if len(obj) > 0: 125 | obj = obj[0] 126 | print_obj(obj) 127 | else: 128 | print("object with label '{}' not found.".format(label)) 129 | 130 | 131 | # ****************************************** 132 | # 133 | # Main experimetns 134 | # 135 | # ****************************************** 136 | 137 | doc = FreeCAD.ActiveDocument 138 | docname = doc.Name 139 | 140 | # ****************************************** 141 | objects = doc.Objects 142 | print("doc.Objects", len(objects)) 143 | print_objects(objects) 144 | 145 | # ****************************************** 146 | 147 | print_obj_with_label(doc, "BodyTest_Part") 148 | print_obj_with_label(doc, "Body_Hex") 149 | print_obj_with_label(doc, "Sphere_Sun") 150 | 151 | # ****************************************** 152 | print("tests done :-)") 153 | -------------------------------------------------------------------------------- /dev/freecad_test_body_objects/list_objects_copypaste__minimal.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | """ 5 | List Objects. 6 | 7 | Copy&Paste this complete script into the FreeCAD Python console! 8 | """ 9 | 10 | 11 | 12 | 13 | 14 | import FreeCAD 15 | print("FreeCAD version:", FreeCAD.Version()) 16 | 17 | objects = FreeCAD.ActiveDocument.Objects 18 | 19 | print("doc.Objects", len(objects)) 20 | 21 | for o in objects: 22 | print(o, o.Name) 23 | -------------------------------------------------------------------------------- /dev/freecad_test_body_objects/python_import_debug.blend: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s-light/io_import_fcstd/ace3fa082fd0b60721a8338680889fafb7c64024/dev/freecad_test_body_objects/python_import_debug.blend -------------------------------------------------------------------------------- /dev/hdr/hdrihaven.com/lakeside_1k.hdr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s-light/io_import_fcstd/ace3fa082fd0b60721a8338680889fafb7c64024/dev/hdr/hdrihaven.com/lakeside_1k.hdr -------------------------------------------------------------------------------- /dev/import_tests.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | """ 5 | Import Tests. 6 | 7 | this script can be called from commandline or from within blender. 8 | therefore we need some heavy tricks with the sys.path.. 9 | """ 10 | 11 | import sys 12 | import os 13 | import importlib 14 | 15 | print("$"*42) 16 | print("import_tests.py") 17 | print("$"*42) 18 | 19 | try: 20 | import bpy 21 | except ModuleNotFoundError as e: 22 | print("Blender 'bpy' not available.", e) 23 | bpy = None 24 | 25 | 26 | # prepare directory helpers 27 | # print(sys.argv[0]) 28 | print(os.path.realpath(__file__)) 29 | script_dir = "." 30 | if bpy: 31 | temp_path = os.path.join(__file__, "..") 32 | temp_path = os.path.join(temp_path, "..") 33 | script_dir = os.path.realpath(temp_path) 34 | script_dir = os.path.realpath(script_dir) 35 | print("script_dir", script_dir) 36 | if script_dir not in sys.path: 37 | sys.path.append(script_dir) 38 | 39 | base_dir = ".." 40 | if bpy: 41 | base_dir = os.path.join(script_dir, "..") 42 | base_dir = os.path.realpath(base_dir) 43 | print("base_dir", base_dir) 44 | if base_dir not in sys.path: 45 | sys.path.append(base_dir) 46 | 47 | outside_package_dir = os.path.join(base_dir, "..") 48 | outside_package_dir = os.path.realpath(outside_package_dir) 49 | print("outside_package_dir", outside_package_dir) 50 | if outside_package_dir not in sys.path: 51 | sys.path.append(outside_package_dir) 52 | 53 | # print("sys.path:") 54 | # for p in sys.path: 55 | # print(p) 56 | 57 | 58 | # fallback path to FreeCAD daily 59 | path_to_freecad = "/usr/lib/freecad-daily-python3/lib/FreeCAD.so" 60 | path_to_system_packages = "/usr/lib/python3/dist-packages/" 61 | 62 | 63 | def append_path(path, sub=""): 64 | if path and sub: 65 | path = os.path.join(path, sub) 66 | print("full path:", path) 67 | if path and os.path.exists(path): 68 | if os.path.isfile(path): 69 | path = os.path.dirname(path) 70 | print("Configured path:", path) 71 | if path not in sys.path: 72 | sys.path.append(path) 73 | else: 74 | print( 75 | "Path does not exist. Please check! " 76 | "'{}'".format(path) 77 | ) 78 | 79 | 80 | def get_preferences(): 81 | """Get addon preferences.""" 82 | __package__ = "io_import_fcstd" 83 | print("__package__: '{}'".format(__package__)) 84 | addon_prefs = None 85 | # pref = bpy.context.preferences.addons["io_import_fcstd"].preferences 86 | if bpy: 87 | user_preferences = bpy.context.preferences 88 | addon_prefs = user_preferences.addons[__package__].preferences 89 | return addon_prefs 90 | 91 | 92 | def get_path_to_freecad(): 93 | """Get FreeCAD path from addon preferences.""" 94 | # get the FreeCAD path specified in addon preferences 95 | addon_prefs = get_preferences() 96 | path = addon_prefs.filepath_freecad 97 | print("addon_prefs path_to freecad", path) 98 | return path 99 | 100 | 101 | def get_path_to_system_packages(): 102 | """Get FreeCAD path from addon preferences.""" 103 | # get the FreeCAD path specified in addon preferences 104 | addon_prefs = get_preferences() 105 | path = addon_prefs.filepath_system_packages 106 | print("addon_prefs path_to system_packages", path) 107 | return path 108 | 109 | 110 | def append_freecad_path(): 111 | """Append the FreeCAD path.""" 112 | global path_to_freecad 113 | global path_to_system_packages 114 | if bpy: 115 | # specified in addon preferences 116 | user_preferences = bpy.context.preferences 117 | addon_prefs = user_preferences.addons["io_import_fcstd"].preferences 118 | path_to_freecad = addon_prefs.filepath_freecad 119 | path_to_freecad = get_path_to_freecad() 120 | path_to_system_packages = get_path_to_system_packages() 121 | append_path(path_to_freecad) 122 | 123 | 124 | try: 125 | append_freecad_path() 126 | import FreeCAD 127 | print("FreeCAD version:", FreeCAD.Version()) 128 | except ModuleNotFoundError as e: 129 | print("FreeCAD import failed.", e) 130 | 131 | # try: 132 | # import Part 133 | # # import Draft 134 | # # import PartDesignGui 135 | # except ModuleNotFoundError as e: 136 | # print("FreeCAD Workbench import failed:", e) 137 | 138 | print("*"*42) 139 | print("from io_import_fcstd import import_fcstd") 140 | # pylama:ignore=E402 141 | from io_import_fcstd import import_fcstd 142 | importlib.reload(import_fcstd) 143 | importlib.reload(import_fcstd.helper) 144 | importlib.reload(import_fcstd.guidata) 145 | importlib.reload(import_fcstd.material) 146 | importlib.reload(import_fcstd.fc_helper) 147 | importlib.reload(import_fcstd.b_helper) 148 | 149 | # ****************************************** 150 | # 151 | # Main experimetns 152 | # 153 | # ****************************************** 154 | 155 | # def run_tests(doc): 156 | # print("~"*42) 157 | # print("get_filtered_objects") 158 | # freecad_helper.print_objects(freecad_helper.get_filtered_objects(doc)) 159 | # 160 | # print("~"*42) 161 | # print("get_root_objects") 162 | # objects = freecad_helper.get_root_objects( 163 | # doc, 164 | # filter_list=['Sketcher::SketchObject', ] 165 | # ) 166 | # freecad_helper.print_objects(objects) 167 | # 168 | # print("~"*42) 169 | # print("doc.RootObjects") 170 | # freecad_helper.print_objects(doc.RootObjects) 171 | # 172 | # print("~"*42) 173 | # print("get_toplevel_objects") 174 | # freecad_helper.print_objects(freecad_helper.get_toplevel_objects(doc)) 175 | # 176 | # print("~"*42) 177 | # print("tests done :-)") 178 | 179 | 180 | def freecad_python_console_copy_and_paste(): 181 | """Copy into FreeCAD python console...""" 182 | # sys.path.append("/home/stefan/mydata/github/blender/io_import_fcstd") 183 | sys.path.append(outside_package_dir) 184 | import freecad_helper as fch # noqa 185 | 186 | 187 | # ****************************************** 188 | def main_test(): 189 | """Run Tests.""" 190 | print("*"*42) 191 | print("run import_tests") 192 | 193 | # get open document name 194 | doc_filename = os.path.splitext(os.path.basename(bpy.data.filepath)) 195 | if doc_filename[1].endswith("blend"): 196 | doc_filename = doc_filename[0] 197 | doc_filename += ".FCStd" 198 | filename = os.path.join(".", doc_filename) 199 | filename = os.path.join(script_dir, filename) 200 | else: 201 | # fallback 202 | # filename = "./dev/freecad_linking_example/assembly.FCStd" 203 | filename = "./dev/freecad_linking_example/assembly__export.FCStd" 204 | filename = os.path.join(base_dir, filename) 205 | 206 | filename = os.path.realpath(filename) 207 | print("FreeCAD document to import:", filename) 208 | 209 | print( 210 | "purge '{}' unused data blocks." 211 | "".format(import_fcstd.b_helper.purge_all_unused()) 212 | ) 213 | print( 214 | "purge '{}' unused data blocks." 215 | "".format(import_fcstd.b_helper.purge_all_unused()) 216 | ) 217 | 218 | my_importer = import_fcstd.ImportFcstd( 219 | # update=True, 220 | # placement=True, 221 | # scale=0.001, 222 | # tessellation=0.10, 223 | # auto_smooth_use=True, 224 | # auto_smooth_angle=math.radians(85), 225 | # skiphidden=True, 226 | # filter_sketch=True, 227 | # sharemats=True, 228 | # update_materials=False, 229 | # obj_name_prefix="", 230 | # obj_name_prefix_with_filename=False, 231 | links_as_collectioninstance=False, 232 | path_to_freecad=path_to_freecad, 233 | path_to_system_packages=path_to_system_packages, 234 | # report=None 235 | ) 236 | my_importer.import_fcstd(filename=filename) 237 | 238 | if bpy: 239 | print("\n"*2) 240 | 241 | 242 | if __name__ == '__main__': 243 | main_test() 244 | -------------------------------------------------------------------------------- /dev/list_objects.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | """ 5 | List Objects 6 | 7 | this script can be called from commandline or from within blender. 8 | therefore we need some heavy tricks with the sys.path.. 9 | """ 10 | 11 | import sys 12 | import os 13 | import importlib 14 | 15 | try: 16 | import bpy 17 | 18 | print("\n" * 2) 19 | except ModuleNotFoundError as e: 20 | print("Blender 'bpy' not available.", e) 21 | bpy = None 22 | 23 | 24 | # prepare directory helpers 25 | # print(sys.argv[0]) 26 | print(os.path.realpath(__file__)) 27 | script_dir = ".." 28 | if bpy: 29 | temp_path = os.path.join(__file__, "..") 30 | temp_path = os.path.join(temp_path, "..") 31 | script_dir = os.path.realpath(temp_path) 32 | script_dir = os.path.realpath(script_dir) 33 | print("script_dir", script_dir) 34 | 35 | base_dir = ".." 36 | if bpy: 37 | base_dir = os.path.join(script_dir, "..") 38 | base_dir = os.path.realpath(base_dir) 39 | print("base_dir", base_dir) 40 | 41 | # Adds base_dir to python modules path. 42 | if base_dir not in sys.path: 43 | sys.path.append(base_dir) 44 | # print("sys.path:") 45 | # for p in sys.path: 46 | # print(p) 47 | 48 | 49 | # fallback path to FreeCAD daily 50 | path_to_freecad = "/usr/lib/freecad-daily-python3/lib/FreeCAD.so" 51 | 52 | 53 | def append_freecad_path(): 54 | """Append the FreeCAD path.""" 55 | global path_to_freecad 56 | if bpy: 57 | # specified in addon preferences 58 | user_preferences = bpy.context.preferences 59 | addon_prefs = user_preferences.addons["io_import_fcstd"].preferences 60 | path_to_freecad = addon_prefs.filepath 61 | if os.path.exists(path_to_freecad): 62 | if os.path.isfile(path_to_freecad): 63 | path_to_freecad = os.path.dirname(path_to_freecad) 64 | print("Configured FreeCAD path:", path_to_freecad) 65 | if path_to_freecad not in sys.path: 66 | sys.path.append(path_to_freecad) 67 | else: 68 | if bpy: 69 | print("FreeCAD path is not configured in preferences correctly.") 70 | else: 71 | print("FreeCAD path is not correct.") 72 | 73 | 74 | try: 75 | append_freecad_path() 76 | import FreeCAD 77 | 78 | print("FreeCAD version:", FreeCAD.Version()) 79 | except ModuleNotFoundError as e: 80 | print("FreeCAD import failed.", e) 81 | 82 | # try: 83 | # import Part 84 | # # import Draft 85 | # # import PartDesignGui 86 | # except ModuleNotFoundError as e: 87 | # print("FreeCAD Workbench import failed:", e) 88 | 89 | 90 | import freecad_helper # noqa 91 | 92 | importlib.reload(freecad_helper) 93 | 94 | 95 | # ****************************************** 96 | # 97 | # Main experimetns 98 | # 99 | # ****************************************** 100 | 101 | doc = None 102 | t1 = None 103 | t2 = None 104 | 105 | 106 | def start_test(): 107 | global doc 108 | doc = FreeCAD.open("./freecad_linking_example/assembly.FCStd") 109 | global t1 110 | t1 = doc.getObjectsByLabel("my_final_assembly")[0] 111 | global t2 112 | t2 = doc.getObjectsByLabel("octagon_part")[0] 113 | 114 | 115 | def run_tests(doc): 116 | print("~" * 42) 117 | objects = freecad_helper.get_filtered_objects(doc) 118 | print("get_filtered_objects", len(objects)) 119 | freecad_helper.print_objects(objects, show_lists=True, show_list_details=True) 120 | 121 | print("~" * 42) 122 | objects, objects_withHost = freecad_helper.get_root_objects( 123 | doc, filter_list=["Sketcher::SketchObject"] 124 | ) 125 | print("get_root_objects", len(objects)) 126 | freecad_helper.print_objects(objects) 127 | print("get_root_objects withHost", len(objects_withHost)) 128 | freecad_helper.print_objects(objects_withHost) 129 | 130 | print("~" * 42) 131 | objects = doc.RootObjects 132 | print("doc.RootObjects", len(objects)) 133 | freecad_helper.print_objects(objects) 134 | 135 | print("~" * 42) 136 | objects = freecad_helper.get_toplevel_objects(doc) 137 | print("get_toplevel_objects", len(objects)) 138 | freecad_helper.print_objects(objects) 139 | 140 | print("~" * 42) 141 | print("tests done :-)") 142 | 143 | 144 | # ****************************************** 145 | def main_test(): 146 | "Main Tests." 147 | if bpy: 148 | print("\n" * 2) 149 | print("*" * 42) 150 | print("run import_tests") 151 | 152 | # Context Managers not implemented.. 153 | # see https://docs.python.org/3.8/reference/compound_stmts.html#with 154 | # with FreeCAD.open(self.config["filename"]) as doc: 155 | # so we use the classic try finally block: 156 | docname = "" 157 | try: 158 | # filename_relative = "./dev/freecad_linking_example/assembly.FCStd" 159 | # filename_relative = ( 160 | # "./dev/freecad_test_ParentChildPositions/TestParentChildPositions.FCStd" 161 | # ) 162 | filename_relative = "./dev/freecad_test_ArchWB/simple_wall_with_door.FCStd" 163 | print("FreeCAD document:", filename_relative) 164 | filename = os.path.join(base_dir, filename_relative) 165 | print("open file..") 166 | doc = FreeCAD.open(filename) 167 | print("file is opened") 168 | docname = doc.Name 169 | if not doc: 170 | print("Unable to open the given FreeCAD file") 171 | else: 172 | run_tests(doc) 173 | except Exception as e: 174 | raise e 175 | finally: 176 | if docname: 177 | FreeCAD.closeDocument(docname) 178 | print("*" * 42) 179 | if bpy: 180 | print("\n" * 2) 181 | 182 | 183 | if __name__ == "__main__": 184 | main_test() 185 | -------------------------------------------------------------------------------- /dev/list_objects_simple.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | """ 5 | List Objects. 6 | 7 | Stand-alone / Copy&Paste version 8 | """ 9 | 10 | import sys 11 | import os 12 | 13 | 14 | def append_freecad_path(): 15 | """Append the FreeCAD path.""" 16 | path_to_freecad = "/usr/lib/freecad-daily-python3/lib/FreeCAD.so" 17 | if os.path.exists(path_to_freecad): 18 | if os.path.isfile(path_to_freecad): 19 | path_to_freecad = os.path.dirname(path_to_freecad) 20 | print("Configured FreeCAD path:", path_to_freecad) 21 | if path_to_freecad not in sys.path: 22 | sys.path.append(path_to_freecad) 23 | else: 24 | print("FreeCAD path is not correct.") 25 | 26 | 27 | try: 28 | try: 29 | import FreeCAD 30 | except ModuleNotFoundError: 31 | append_freecad_path() 32 | import FreeCAD 33 | print("FreeCAD version:", FreeCAD.Version()) 34 | except ModuleNotFoundError as e: 35 | print("FreeCAD import failed.", e) 36 | 37 | 38 | # ****************************************** 39 | 40 | def print_obj_header( 41 | pre_line="", 42 | show_lists=False, 43 | show_list_details=False, 44 | ): 45 | """Print header for objects list.""" 46 | print( 47 | pre_line + 48 | "{:<15} {:<25} {:<25}" 49 | "".format("Label", "Name", "TypeId"), 50 | end='' 51 | ) 52 | if show_lists: 53 | print( 54 | "{:>2} {:>2} {:>2} {:>2}" 55 | "".format( 56 | "P", 57 | "I", 58 | "O", 59 | "G", 60 | ), 61 | end='' 62 | ) 63 | if show_list_details: 64 | print( 65 | ( 66 | " {:<10}" * 4 67 | ).format( 68 | '[Parents]', 69 | '[InList]', 70 | '[OutList]', 71 | '[Group]' 72 | ), 73 | end='' 74 | ) 75 | print() 76 | 77 | 78 | def print_obj( 79 | obj, 80 | pre_line="", 81 | show_lists=False, 82 | show_list_details=False, 83 | end="\n", 84 | ): 85 | """Print object nicely formated.""" 86 | print( 87 | pre_line + 88 | "{:<25} {:<15} {:<25}" 89 | "".format(obj.Label, obj.Name, obj.TypeId), 90 | end='' 91 | ) 92 | if show_lists: 93 | group_count = '_' 94 | if hasattr(obj, 'Group'): 95 | group_count = len(obj.Group) 96 | print( 97 | "{:>2} {:>2} {:>2} {:>2}" 98 | "".format( 99 | len(obj.Parents), 100 | len(obj.InList), 101 | len(obj.OutList), 102 | group_count 103 | ), 104 | end='' 105 | ) 106 | if show_list_details: 107 | group = None 108 | if hasattr(obj, 'Group'): 109 | group = obj.Group 110 | print( 111 | ( 112 | " {:<10}" * 4 113 | ).format( 114 | str(obj.Parents), 115 | str(obj.InList), 116 | str(obj.OutList), 117 | str(group) 118 | ), 119 | end='' 120 | ) 121 | print("", end=end) 122 | 123 | 124 | def print_objects( 125 | objects, 126 | pre_line="", 127 | pre_list_entry="* ", 128 | show_lists=False, 129 | show_list_details=False, 130 | ): 131 | """Print objects list.""" 132 | pre_list_entry_space = " "*len(pre_list_entry) 133 | print_obj_header( 134 | pre_line=pre_line + pre_list_entry_space, 135 | show_lists=show_lists, 136 | show_list_details=show_list_details, 137 | ) 138 | for obj in objects: 139 | print_obj( 140 | obj, 141 | pre_line=pre_line + pre_list_entry, 142 | show_lists=show_lists, 143 | show_list_details=show_list_details, 144 | ) 145 | 146 | 147 | def print_obj_with_label(doc, label): 148 | """Print object with given label.""" 149 | obj = doc.getObjectsByLabel(label) 150 | # print(obj) 151 | if len(obj) > 0: 152 | obj = obj[0] 153 | print_obj(obj) 154 | else: 155 | print("object with label '{}' not found.".format(label)) 156 | 157 | 158 | # ****************************************** 159 | # 160 | # Main experimetns 161 | # 162 | # ****************************************** 163 | 164 | doc = FreeCAD.open( 165 | "/home/stefan/mydata/github/blender/" 166 | "io_import_fcstd/" 167 | # "dev/freecad_linking_example/assembly.FCStd" 168 | # "dev/freecad_test_MyLittleWorld/MyLittleWorld.FCStd" 169 | "dev/freecad_test_body_objects/BodyTest.FCStd" 170 | ) 171 | docname = doc.Name 172 | 173 | # ****************************************** 174 | print("~"*42) 175 | objects = doc.Objects 176 | print("doc.Objects", len(objects)) 177 | print_objects(objects) 178 | print("~"*42) 179 | 180 | print_obj_with_label(doc, "my_final_assembly") 181 | print_obj_with_label(doc, "octagon_part") 182 | print_obj_with_label(doc, "octagon_body") 183 | 184 | # t1 = doc.getObjectsByLabel("my_final_assembly") 185 | # t2 = doc.getObjectsByLabel("octagon_part") 186 | # t3 = doc.getObjectsByLabel("octagon_body") 187 | 188 | 189 | print("tests done :-)") 190 | 191 | FreeCAD.closeDocument(docname) 192 | -------------------------------------------------------------------------------- /dev/macro_copySimpleExtended.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """create a simplifyed copy of selected objects.""" 4 | 5 | # FreeCAD basics 6 | import FreeCAD 7 | import FreeCADGui 8 | import ImportGui 9 | 10 | # regex 11 | import re 12 | 13 | import time 14 | import os.path 15 | 16 | ##################### 17 | # Begin command Part_SimpleCopy 18 | # doc = App.getDocument('lamp') 19 | # obj = doc.getObject('Body001') 20 | # __shape = Part.getShape( 21 | # obj, 22 | # '', 23 | # needSubElement=False, 24 | # refine=False 25 | # ) 26 | # App.ActiveDocument.addObject('Part::Feature', 'Body001').Shape = __shape 27 | # App.ActiveDocument.ActiveObject.Label = obj.Label 28 | # obj_new = doc.getObject('Body001001') 29 | # 30 | # obj_new.ViewObject.ShapeColor = \ 31 | # getattr( 32 | # obj.getLinkedObject(True).ViewObject, 33 | # 'ShapeColor', 34 | # obj_new.ViewObject.ShapeColor 35 | # ) 36 | # obj_new.ViewObject.LineColor = \ 37 | # getattr( 38 | # obj.getLinkedObject(True).ViewObject, 39 | # 'LineColor', 40 | # obj_new.ViewObject.LineColor 41 | # ) 42 | # obj_new.ViewObject.PointColor = \ 43 | # getattr( 44 | # obj.getLinkedObject(True).ViewObject, 45 | # 'PointColor', 46 | # obj_new.ViewObject.PointColor 47 | # ) 48 | # App.ActiveDocument.recompute() 49 | # End command Part_SimpleCopy 50 | ##################### 51 | 52 | 53 | def object_create_copy(obj_source): 54 | """Create a copy of an object.""" 55 | obj_new = App.ActiveDocument.addObject( 56 | 'Part::Feature', 57 | obj_source.Name + "__sc_export" 58 | ) 59 | __shape_refined = Part.getShape( 60 | obj_source, 61 | '', 62 | needSubElement=False, 63 | refine=False 64 | ) 65 | obj_new.Shape = __shape_refined 66 | obj_new.Label = obj_source.Label + "__sc_export" 67 | print(obj_source) 68 | 69 | # AttributeError: 'Part.Feature' object has no attribute 'BoundingBox' 70 | obj_new.ViewObject.BoundingBox = obj_source.ViewObject.BoundingBox 71 | obj_new.ViewObject.Deviation = obj_source.ViewObject.Deviation 72 | obj_new.ViewObject.DisplayMode = obj_source.ViewObject.DisplayMode 73 | obj_new.ViewObject.DrawStyle = obj_source.ViewObject.DrawStyle 74 | obj_new.ViewObject.Lighting = obj_source.ViewObject.Lighting 75 | obj_new.ViewObject.LineColor = obj_source.ViewObject.LineColor 76 | obj_new.ViewObject.LineMaterial = obj_source.ViewObject.LineMaterial 77 | obj_new.ViewObject.LineWidth = obj_source.ViewObject.LineWidth 78 | obj_new.ViewObject.PointColor = obj_source.ViewObject.PointColor 79 | obj_new.ViewObject.PointMaterial = obj_source.ViewObject.PointMaterial 80 | obj_new.ViewObject.PointSize = obj_source.ViewObject.PointSize 81 | obj_new.ViewObject.Selectable = obj_source.ViewObject.Selectable 82 | obj_new.ViewObject.ShapeColor = obj_source.ViewObject.ShapeColor 83 | obj_new.ViewObject.ShapeMaterial = obj_source.ViewObject.ShapeMaterial 84 | obj_new.ViewObject.Transparency = obj_source.ViewObject.Transparency 85 | obj_new.ViewObject.Visibility = obj_source.ViewObject.Visibility 86 | return obj_new 87 | 88 | 89 | def find_Parent(obj): 90 | """Find Parent Part object for obj.""" 91 | result_obj = None 92 | # this findes the 'last' Part.. 93 | # but as fare as i know there should only be one in this list.. 94 | for x in obj.InList: 95 | if ( 96 | x.isDerivedFrom("App::Part") 97 | ): 98 | result_obj = x 99 | return result_obj 100 | 101 | 102 | def simpleCopySelection(): 103 | """Create a simplifyed copy of selected objects.""" 104 | # ideas / tests / original: 105 | # push into current group.. 106 | 107 | App = FreeCAD 108 | Gui = FreeCADGui 109 | 110 | selection = FreeCADGui.Selection.getSelection() 111 | 112 | for obj in selection: 113 | obj_new = object_create_copy(obj) 114 | obj_new.ViewObject.Visibility = True 115 | obj.ViewObject.Visibility = False 116 | # try to add it at same tree location 117 | obj_parent = find_Parent(obj) 118 | if obj_parent: 119 | obj_parent.addObject(obj_new) 120 | 121 | # 122 | 123 | App.ActiveDocument.recompute() 124 | # 125 | 126 | 127 | # just do it: 128 | simpleCopySelection() 129 | -------------------------------------------------------------------------------- /dev/script_test.blend: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s-light/io_import_fcstd/ace3fa082fd0b60721a8338680889fafb7c64024/dev/script_test.blend -------------------------------------------------------------------------------- /dev/temp_python3_cmd_copyAndpaste.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | 4 | path = "/usr/lib/freecad/lib" 5 | sys.path.append(path) 6 | 7 | import FreeCAD 8 | print("FreeCAD version:", FreeCAD.Version()) 9 | 10 | path_base = FreeCAD.getResourceDir() 11 | path = os.path.join(path_base, "Mod") 12 | sys.path.append(path) 13 | 14 | doc = FreeCAD.open("./BodyTest_Minimal.FCStd") 15 | docname = doc.Name 16 | objects = FreeCAD.ActiveDocument.Objects 17 | 18 | print("doc.Objects", len(objects)) 19 | 20 | for o in objects: 21 | print(o, o.Name) 22 | 23 | FreeCAD.closeDocument(docname) 24 | -------------------------------------------------------------------------------- /freecad_helper.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | """Helper tools for FreeCAD python scripts.""" 5 | 6 | 7 | # import blender_helper as b_helper 8 | 9 | 10 | def print_obj_header( 11 | pre_line="", show_lists=False, show_list_details=False, 12 | ): 13 | """Print header for objects list.""" 14 | print( 15 | pre_line + "{:<25} {:<15} {:<25}" "".format("Label", "Name", "TypeId"), end="", 16 | ) 17 | if show_lists: 18 | print( 19 | "{:>2} {:>2} {:>2} {:>2} {:>2}" "".format("P", "I", "O", "G", "H",), end="" 20 | ) 21 | if show_list_details: 22 | print( 23 | (" {:<30}" * 5).format( 24 | "[Parents]", "[InList]", "[OutList]", "[Group]", "[Hosts]", 25 | ), 26 | # b_helper.colors.fg.lightblue + "[Parents]", 27 | # b_helper.colors.fg.lightred + "[InList]", 28 | # b_helper.colors.fg.yellow + "[OutList]", 29 | # b_helper.colors.fg.pink + "[Group]", 30 | # b_helper.colors.fg.lightgreen + "[Hosts]", 31 | # ) 32 | # + b_helper.colors.reset, 33 | end="", 34 | ) 35 | print() 36 | 37 | 38 | def format_obj_show_lists(obj, show_list_details): 39 | result = "" 40 | group_count = "_" 41 | if hasattr(obj, "Group"): 42 | group_count = len(obj.Group) 43 | hosts_count = "_" 44 | if hasattr(obj, "Hosts"): 45 | hosts_count = len(obj.Hosts) 46 | result += "{:>2} {:>2} {:>2} {:>2} {:>2}" "".format( 47 | len(obj.Parents), len(obj.InList), len(obj.OutList), group_count, hosts_count, 48 | ) 49 | if show_list_details: 50 | group = None 51 | if hasattr(obj, "Group"): 52 | group = obj.Group 53 | hosts = None 54 | if hasattr(obj, "Hosts"): 55 | hosts = obj.Hosts 56 | result += (" {:<30}" * 5).format( 57 | str(obj.Parents), str(obj.InList), str(obj.OutList), str(group), str(hosts), 58 | ) 59 | # b_helper.colors.fg.lightblue + str(obj.Parents), 60 | # b_helper.colors.fg.lightred + str(obj.InList), 61 | # b_helper.colors.fg.yellow + str(obj.OutList), 62 | # b_helper.colors.fg.pink + str(group), 63 | # b_helper.colors.fg.lightgreen + str(hosts), 64 | # ) + b_helper.colors.reset 65 | return result 66 | 67 | 68 | def format_obj( 69 | obj, pre_line="", show_lists=False, show_list_details=False, tight_format=False 70 | ): 71 | """Print object nicely formated.""" 72 | result = "" 73 | obj_label = "NONE" 74 | if obj: 75 | obj_label = obj.Label 76 | obj_name = "NONE" 77 | if obj: 78 | obj_name = obj.Name 79 | obj_type = "NONE" 80 | if obj: 81 | obj_type = obj.TypeId 82 | obj_format = "{:<25} {:<15} {:<25}" 83 | if tight_format: 84 | obj_format = "'{}' ('{}' <{}>)" 85 | result += pre_line + obj_format.format(obj_label, obj_name, obj_type) 86 | if show_lists: 87 | result += format_obj_show_lists(obj, show_list_details) 88 | return result 89 | 90 | 91 | def print_obj( 92 | obj, pre_line="", show_lists=False, show_list_details=False, end="\n", 93 | ): 94 | print( 95 | format_obj( 96 | obj=obj, 97 | pre_line=pre_line, 98 | show_lists=show_lists, 99 | show_list_details=show_list_details, 100 | ), 101 | end=end, 102 | ) 103 | 104 | 105 | def print_objects( 106 | objects, 107 | pre_line="", 108 | pre_list_entry="* ", 109 | show_lists=False, 110 | show_list_details=False, 111 | ): 112 | """Print objects list.""" 113 | pre_list_entry_space = " " * len(pre_list_entry) 114 | print_obj_header( 115 | pre_line=pre_line + pre_list_entry_space, 116 | show_lists=show_lists, 117 | show_list_details=show_list_details, 118 | ) 119 | for index, obj in enumerate(objects): 120 | # line_color = b_helper.colors.reset 121 | # if index % 2: 122 | # # line_color = b_helper.colors.bg.lightgrey + b_helper.colors.fg.black 123 | # line_color = b_helper.colors.bg.black 124 | print_obj( 125 | obj, 126 | pre_line=pre_line + pre_list_entry, 127 | # pre_line=pre_line + pre_list_entry + line_color, 128 | show_lists=show_lists, 129 | show_list_details=show_list_details, 130 | # end=b_helper.colors.reset + "\n", 131 | ) 132 | 133 | 134 | # **************************************** 135 | 136 | 137 | def filtered_objects(objects, typeid_filter_list=None, include_only_visible=False): 138 | """Filter list of objects.""" 139 | if typeid_filter_list is None: 140 | typeid_filter_list = [ 141 | "App::Line", 142 | "App::Plane", 143 | "App::Origin", 144 | # 'GeoFeature', 145 | # 'PartDesign::CoordinateSystem', 146 | # 'Sketcher::SketchObject', 147 | ] 148 | result_objects = [] 149 | for obj in objects: 150 | if obj.TypeId not in typeid_filter_list: 151 | if include_only_visible: 152 | if hasattr(obj, "Visibility"): 153 | if obj.Visibility: 154 | result_objects.append(obj) 155 | else: 156 | print( 157 | "filtered_objects: " 158 | "obj '{}' has no Visibility attribute." 159 | "it is excluded from results." 160 | "".format(obj.Name) 161 | ) 162 | else: 163 | result_objects.append(obj) 164 | return result_objects 165 | 166 | 167 | def get_filtered_objects(doc, typeid_filter_list=None): 168 | """Get filterd list of objects.""" 169 | result_objects = filtered_objects(doc.Objects, typeid_filter_list) 170 | return result_objects 171 | 172 | 173 | def get_root_objects(doc, filter_list=[]): 174 | """Get root list of objects.""" 175 | typeid_filter_list = [ 176 | "App::Line", 177 | "App::Plane", 178 | "App::Origin", 179 | ] 180 | typeid_filter_list = typeid_filter_list + filter_list 181 | result_objects = [] 182 | result_objects_withHost = [] 183 | for obj in doc.Objects: 184 | if obj.TypeId not in typeid_filter_list: 185 | # if len(obj.Parents) == 0 and len(object_get_HostChilds(obj) == 0): 186 | # result_objects.append(obj) 187 | if len(obj.Parents) == 0: 188 | if hasattr(obj, "Hosts"): 189 | if len(obj.Hosts) == 0: 190 | result_objects.append(obj) 191 | else: 192 | result_objects_withHost.append(obj) 193 | else: 194 | result_objects.append(obj) 195 | return result_objects, result_objects_withHost 196 | 197 | 198 | def object_get_HostChilds(obj): 199 | """Return List of Objects that have set Host(s) to this object.""" 200 | # source: 201 | # FreeCAD/src/Mod/Arch/ArchComponent.py 202 | # https://github.com/FreeCAD/FreeCAD/blob/master/src/Mod/Arch/ArchComponent.py#L1109 203 | # def getHosts(self,obj) 204 | hosts = [] 205 | 206 | for link in obj.InListRecursive: 207 | if hasattr(link, "Host"): 208 | if link.Host: 209 | if link.Host == obj: 210 | hosts.append(link) 211 | elif hasattr(link, "Hosts"): 212 | if link.Hosts: 213 | if obj in link.Hosts: 214 | hosts.append(link) 215 | return hosts 216 | 217 | 218 | # ****************************************** 219 | # `is_toplevel_in_list` and `get_toplevel_objects` 220 | # from forum post 'Get highest objects of model' by kbwbe 221 | # https://forum.freecadweb.org/viewtopic.php?p=338214&sid=a6dd59fe66c1d807f8537f192fdb14dc#p338214 222 | 223 | 224 | def is_toplevel_in_list(lst): 225 | """Check if objects in list are at top level.""" 226 | if len(lst) == 0: 227 | return True 228 | for ob in lst: 229 | if ob.Name.startswith("Clone"): 230 | continue 231 | if ob.Name.startswith("Part__Mirroring"): 232 | continue 233 | else: 234 | return False 235 | return True 236 | 237 | 238 | def get_toplevel_objects(doc): 239 | """Get top level list of objects.""" 240 | topLevelShapes = [] 241 | for ob in doc.Objects: 242 | if is_toplevel_in_list(ob.InList): 243 | topLevelShapes.append(ob) 244 | else: 245 | numBodies = 0 246 | numClones = 0 247 | invalidObjects = False 248 | # perhaps pairs of Clone/Bodies 249 | if len(ob.InList) % 2 == 0: 250 | for o in ob.InList: 251 | if o.Name.startswith("Clone"): 252 | numClones += 1 253 | elif o.Name.startswith("Body"): 254 | numBodies += 1 255 | else: 256 | invalidObjects = True 257 | break 258 | if not invalidObjects: 259 | if numBodies == numClones: 260 | topLevelShapes.append(ob.Name) 261 | return topLevelShapes 262 | -------------------------------------------------------------------------------- /import_fcstd/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | """Import FreeCAD files to blender.""" 5 | 6 | import sys 7 | import bpy 8 | import os 9 | import math 10 | 11 | # import pprint 12 | 13 | from .. import freecad_helper as fc_helper 14 | from .. import blender_helper as b_helper 15 | 16 | from . import helper 17 | from . import guidata 18 | from .material import MaterialManager 19 | 20 | 21 | # set to True to triangulate all faces (will loose multimaterial info) 22 | TRIANGULATE = False 23 | 24 | 25 | class ImportFcstd(object): 26 | """Import fcstd files.""" 27 | 28 | def __init__( 29 | self, 30 | *, # this forces named_properties.. 31 | # filename=None, 32 | update=True, 33 | update_only_modified_meshes=True, 34 | placement=True, 35 | scale=0.001, 36 | tessellation=0.10, 37 | auto_smooth_use=True, 38 | auto_smooth_angle=math.radians(85), 39 | skiphidden=True, 40 | filter_sketch=True, 41 | sharemats=True, 42 | update_materials=False, 43 | obj_name_prefix="", 44 | obj_name_prefix_with_filename=False, 45 | links_as_collectioninstance=True, 46 | path_to_freecad=None, 47 | path_to_system_packages=None, 48 | report=None, 49 | ): 50 | """Init.""" 51 | super(ImportFcstd, self).__init__() 52 | self.config = { 53 | "filename": None, 54 | "update": update, 55 | "update_only_modified_meshes": update_only_modified_meshes, 56 | "placement": placement, 57 | "tessellation": tessellation, 58 | "auto_smooth_use": auto_smooth_use, 59 | "auto_smooth_angle": auto_smooth_angle, 60 | "skiphidden": skiphidden, 61 | "filter_sketch": filter_sketch, 62 | "scale": scale, 63 | "sharemats": sharemats, 64 | "update_materials": update_materials, 65 | "obj_name_prefix_with_filename": obj_name_prefix_with_filename, 66 | "obj_name_prefix": obj_name_prefix, 67 | "links_as_collectioninstance": links_as_collectioninstance, 68 | "report": self.print_report, 69 | } 70 | self.path_to_freecad = path_to_freecad 71 | self.path_to_system_packages = path_to_system_packages 72 | self.report = report 73 | 74 | print("config", self.config) 75 | self.doc = None 76 | self.doc_filename = None 77 | self.guidata = {} 78 | 79 | self.fcstd_collection = None 80 | self.link_targets = None 81 | self.fcstd_empty = None 82 | 83 | self.imported_obj_names = [] 84 | 85 | self.typeid_filter_list = [ 86 | "GeoFeature", 87 | "PartDesign::CoordinateSystem", 88 | ] 89 | if self.config["filter_sketch"]: 90 | self.typeid_filter_list.append("Sketcher::SketchObject") 91 | 92 | def print_report(self, mode, data, pre_line=""): 93 | """Multi print handling.""" 94 | b_helper.print_multi( 95 | mode=mode, data=data, pre_line=pre_line, report=self.report, 96 | ) 97 | 98 | def format_obj(self, obj, pre_line="", post_line=""): 99 | """Print object with nice formating.""" 100 | message = "" 101 | 102 | message = fc_helper.format_obj( 103 | obj=obj, 104 | pre_line=pre_line, 105 | show_lists=False, 106 | show_list_details=False, 107 | tight_format=True, 108 | ) 109 | message += post_line 110 | return message 111 | 112 | def print_obj(self, obj, pre_line="", post_line="", end="\n"): 113 | """Print object with nice formating.""" 114 | message = "" 115 | 116 | message = self.format_obj(obj=obj) 117 | message += post_line 118 | # print(message, end=end) 119 | self.config["report"]({"INFO"}, message, pre_line) 120 | 121 | def print_debug_report(self): 122 | """print out some minimal debug things..""" 123 | print("print_debug_report") 124 | import FreeCAD 125 | print("FreeCAD version:", FreeCAD.Version()) 126 | objects = FreeCAD.ActiveDocument.Objects 127 | print("doc.Objects", len(objects)) 128 | for o in objects: 129 | print(o, o.Name) 130 | 131 | def handle_label_prefix(self, label): 132 | """Handle all label prefix processing.""" 133 | if label: 134 | prefix = self.config["obj_name_prefix"] 135 | if self.config["obj_name_prefix_with_filename"]: 136 | prefix = self.doc.Name + "__" + prefix 137 | if prefix: 138 | label = prefix + "__" + label 139 | return label 140 | 141 | def get_obj_label(self, obj): 142 | """Get object label with optional prefix.""" 143 | label = None 144 | if obj: 145 | # obj_label = "NONE" 146 | # obj_label = obj.Label 147 | # label = obj_label 148 | label = obj.Label 149 | label = self.handle_label_prefix(label) 150 | return label 151 | 152 | def get_obj_link_target_label(self, obj): 153 | """Get object label with optional prefix.""" 154 | label = None 155 | if obj: 156 | label = obj.Label + "__lt" 157 | label = self.handle_label_prefix(label) 158 | return label 159 | 160 | def get_obj_linkedobj_label(self, obj): 161 | """Get linkedobject label with optional prefix.""" 162 | label = None 163 | if hasattr(obj, "LinkedObject"): 164 | label = "NONE" 165 | if obj.LinkedObject: 166 | label = obj.LinkedObject.Label 167 | label = self.handle_label_prefix(label) 168 | return label 169 | 170 | def get_obj_combined_label(self, parent_obj, obj): 171 | """Get object label with optional prefix.""" 172 | label = None 173 | obj_label = "NONE" 174 | parent_obj_label = "NONE" 175 | if obj: 176 | obj_label = obj.Label 177 | if parent_obj: 178 | parent_obj_label = parent_obj.Label 179 | label = parent_obj_label + "." + obj_label 180 | label = self.handle_label_prefix(label) 181 | return label 182 | 183 | # def get_sub_obj_label(self, pre_line, func_data, parent_obj, obj): 184 | def get_sub_obj_label(self, pre_line, func_data, obj): 185 | """Get sub object label.""" 186 | # print( 187 | # pre_line 188 | # + "get_sub_obj_label" 189 | # ) 190 | # pre_line += ". " 191 | # print( 192 | # pre_line 193 | # + "func_data['obj_label']: " 194 | # + b_helper.colors.fg.orange 195 | # + "'{}'".format(func_data["obj_label"]) 196 | # + b_helper.colors.reset 197 | # ) 198 | # print( 199 | # pre_line 200 | # + "func_data['link_source']: " 201 | # + b_helper.colors.fg.orange 202 | # + self.format_obj(func_data["link_source"]) 203 | # + b_helper.colors.reset 204 | # ) 205 | # print( 206 | # pre_line 207 | # + "parent_obj " 208 | # + b_helper.colors.fg.orange 209 | # + self.format_obj(parent_obj) 210 | # + b_helper.colors.reset 211 | # ) 212 | # print( 213 | # pre_line 214 | # + " obj " 215 | # + b_helper.colors.fg.orange 216 | # + self.format_obj(obj) 217 | # + b_helper.colors.reset 218 | # ) 219 | # obj_label = self.get_obj_combined_label(parent_obj, obj) 220 | # if func_data["obj_label"]: 221 | # obj_label = ( 222 | # func_data["obj_label"] 223 | # + "." 224 | # + obj_label 225 | # ) 226 | obj_label = self.get_obj_label(obj) 227 | if func_data["link_source"]: 228 | obj_label = ( 229 | # func_data["obj_label"] 230 | self.get_obj_label(func_data["link_source"]) 231 | + "." 232 | + obj_label 233 | ) 234 | return obj_label 235 | 236 | def fix_link_target_name(self, bobj): 237 | """Fix name of link target object.""" 238 | bobj.name = bobj.name + "__lt" 239 | return bobj.name 240 | 241 | def check_obj_visibility(self, obj): 242 | """Check if obj is visible.""" 243 | result = True 244 | if obj.Name in self.guidata and "Visibility" in self.guidata[obj.Name]: 245 | if self.guidata[obj.Name]["Visibility"] is False: 246 | result = False 247 | return result 248 | 249 | def check_obj_visibility_with_skiphidden(self, obj, obj_visibility=None): 250 | """Check if obj is visible.""" 251 | result = True 252 | if self.config["skiphidden"]: 253 | # print("obj_visibility: '{}'".format(obj_visibility)) 254 | if obj_visibility is not None: 255 | result = obj_visibility 256 | else: 257 | result = self.check_obj_visibility(obj) 258 | return result 259 | 260 | def check_collections_for_bobj(self, bobj): 261 | """Search all collections for given bobj.""" 262 | found_in_collections = None 263 | for col in bpy.data.collections: 264 | if bobj.name in col.objects: 265 | if found_in_collections is None: 266 | found_in_collections = [] 267 | found_in_collections.append(col.name) 268 | return found_in_collections 269 | 270 | # ########################################## 271 | # object handling 272 | 273 | def hascurves(self, shape): 274 | """Check if shape has curves.""" 275 | import Part 276 | 277 | for e in shape.Edges: 278 | if not isinstance(e.Curve, (Part.Line, Part.LineSegment)): 279 | return True 280 | return False 281 | 282 | def handle_placement( 283 | self, 284 | pre_line, 285 | obj, 286 | bobj, 287 | # enable_import_scale=False, 288 | enable_scale=True, 289 | relative=False, 290 | negative=False, 291 | ): 292 | """Handle placement.""" 293 | if self.config["placement"]: 294 | # print(pre_line) 295 | # print(pre_line + " §§§ §§§ handle_placement: '{}'".format(bobj.name)) 296 | # print(pre_line) 297 | new_loc = obj.Placement.Base * self.config["scale"] 298 | # attention: multiply does in-place change. 299 | # so if you call it multiple times on the same value 300 | # you get really strange results... 301 | # new_loc = obj.Placement.Base.multiply(self.config["scale"]) 302 | if relative: 303 | # print( 304 | # "x: {} + {} = {}" 305 | # "".format( 306 | # bobj.location.x, 307 | # new_loc.x, 308 | # bobj.location.x + new_loc.x 309 | # ) 310 | # ) 311 | if negative: 312 | bobj.location.x = bobj.location.x - new_loc.x 313 | bobj.location.y = bobj.location.y - new_loc.y 314 | bobj.location.z = bobj.location.z - new_loc.z 315 | else: 316 | bobj.location.x = bobj.location.x + new_loc.x 317 | bobj.location.y = bobj.location.y + new_loc.y 318 | bobj.location.z = bobj.location.z + new_loc.z 319 | else: 320 | bobj.location = new_loc 321 | m = bobj.rotation_mode 322 | bobj.rotation_mode = "QUATERNION" 323 | if obj.Placement.Rotation.Angle: 324 | # FreeCAD Quaternion is XYZW while Blender is WXYZ 325 | q = (obj.Placement.Rotation.Q[3],) + obj.Placement.Rotation.Q[:3] 326 | bobj.rotation_quaternion = q 327 | bobj.rotation_mode = m 328 | if enable_scale and ("Scale" in obj.PropertiesList): 329 | # object has Scale property so lets use it :-) 330 | bobj.scale = bobj.scale * obj.Scale 331 | 332 | def reset_placement_position(self, bobj): 333 | """Reset placement position.""" 334 | bobj.location.x = 0 335 | bobj.location.y = 0 336 | bobj.location.z = 0 337 | 338 | def update_tree_collections(self, func_data): 339 | """Update object tree.""" 340 | pre_line = func_data["pre_line"] 341 | bobj = func_data["bobj"] 342 | # col = self.check_collections_for_bobj(bobj) 343 | if func_data["collection"]: 344 | add_to_collection = False 345 | if self.config["update"]: 346 | if bobj.name not in func_data["collection"].objects: 347 | add_to_collection = True 348 | else: 349 | # print( 350 | # pre_line + 351 | # "'{}' already in collection '{}'" 352 | # "".format(bobj.name, func_data["collection"]) 353 | # ) 354 | pass 355 | else: 356 | add_to_collection = True 357 | 358 | if add_to_collection: 359 | func_data["collection"].objects.link(bobj) 360 | # print( 361 | # pre_line + 362 | # "'{}' add (tree_collections) to '{}' " 363 | # "".format(bobj, func_data["collection"]) 364 | # ) 365 | if not self.check_collections_for_bobj(bobj): 366 | # link to import collection - so that the object is visible. 367 | collection = self.fcstd_collection 368 | collection.objects.link(bobj) 369 | print( 370 | pre_line + "'{}' add (tree_parents) to '{}' " 371 | "".format(bobj, collection) 372 | ) 373 | 374 | def update_tree_parents(self, func_data): 375 | """Update object tree.""" 376 | pre_line = func_data["pre_line"] 377 | bobj = func_data["bobj"] 378 | # print(pre_line + "update_tree_parents") 379 | # print(pre_line + " bobj.parent '{}'".format(bobj.parent)) 380 | # print( 381 | # pre_line + " func_data[parent_bobj] '{}'".format(func_data["parent_bobj"]) 382 | # ) 383 | if bobj.parent is None and func_data["parent_bobj"] is not None: 384 | print( 385 | pre_line + "update_tree_parents" + " obj '{}' set parent to '{}' " 386 | "".format(bobj, func_data["parent_bobj"]) 387 | ) 388 | # print( 389 | # pre_line + " obj '{}' set parent to '{}' " 390 | # "".format(bobj, func_data["parent_bobj"]) 391 | # ) 392 | bobj.parent = func_data["parent_bobj"] 393 | # TODO: check 'update' 394 | 395 | def create_bmesh_from_func_data( 396 | self, func_data, obj_label, enable_import_scale=True 397 | ): 398 | """Create new object from bmesh.""" 399 | bmesh = bpy.data.meshes.new(name=obj_label) 400 | bmesh.from_pydata(func_data["verts"], func_data["edges"], func_data["faces"]) 401 | bmesh.update() 402 | # handle import scalling 403 | if enable_import_scale: 404 | scale = self.config["scale"] 405 | for v in bmesh.vertices: 406 | v.co *= scale 407 | bmesh.update() 408 | bmesh["freecad_mesh_hash"] = func_data["freecad_mesh_hash"] 409 | return bmesh 410 | 411 | def create_bobj_from_bmesh(self, func_data, obj_label, bmesh): 412 | """Create new object from bmesh.""" 413 | bobj = bpy.data.objects.new(obj_label, bmesh) 414 | # check if we already used the bmesh. 415 | # if bmesh.name in bpy.data.meshes: 416 | # print( 417 | # func_data["pre_line"] + 418 | # " ignore material import. mesh already existed." 419 | # ) 420 | # else: 421 | if len(bmesh.materials) <= 0: 422 | material_manager = MaterialManager( 423 | guidata=self.guidata, 424 | func_data=func_data, 425 | bobj=bobj, 426 | obj_label=obj_label, 427 | sharemats=self.config["sharemats"], 428 | report=self.config["report"], 429 | report_preline=func_data["pre_line"] + "| ", 430 | ) 431 | material_manager.create_new() 432 | else: 433 | print( 434 | func_data["pre_line"] 435 | + " ignore material import. mesh already has material." 436 | ) 437 | func_data["bobj"] = bobj 438 | return bobj 439 | 440 | def create_or_get_bmesh(self, pre_line, func_data, mesh_label): 441 | """Create or get bmesh.""" 442 | pre_line_orig = func_data["pre_line"] 443 | print(pre_line_orig + "create_or_get_bmesh") 444 | pre_line = pre_line_orig + " " 445 | func_data["pre_line"] = pre_line 446 | 447 | bmesh = None 448 | # bmesh_old_name = None 449 | bmesh_import = True 450 | 451 | print(pre_line + "mesh_label:", mesh_label) 452 | # print(pre_line + "bpy.data.meshes ({})".format(len(bpy.data.meshes))) 453 | # for mesh in bpy.data.meshes: 454 | # print(pre_line + " - ", mesh) 455 | if mesh_label in bpy.data.meshes: 456 | bmesh = bpy.data.meshes[mesh_label] 457 | print(pre_line + "use found bmesh.") 458 | bmesh_import = False 459 | # print( 460 | # pre_line 461 | # + "bmesh.freecad_mesh_hash ", 462 | # bmesh.get("freecad_mesh_hash", None) 463 | # ) 464 | # print( 465 | # pre_line 466 | # + "func_data[freecad_mesh_hash] ", 467 | # func_data["freecad_mesh_hash"] 468 | # ) 469 | # print(pre_line + "mesh_label", mesh_label) 470 | # print(pre_line + "self.imported_obj_names") 471 | # for obj_name in self.imported_obj_names: 472 | # print(pre_line + " - ", obj_name) 473 | if mesh_label not in self.imported_obj_names and self.config["update"]: 474 | if self.config["update_only_modified_meshes"]: 475 | print(pre_line + "update_only_modified_meshes: TODO") 476 | # bmesh.get("freecad_mesh_hash", None) 477 | # func_data["freecad_mesh_hash"] 478 | # rename old mesh - 479 | # this way the new mesh can get the original name. 480 | helper.rename_old_data(bpy.data.meshes, mesh_label) 481 | # bmesh_old_name = helper.rename_old_data(bpy.data.meshes, mesh_label) 482 | bmesh_import = True 483 | # create bmesh 484 | if bmesh_import: 485 | print(pre_line + "import bmesh.") 486 | bmesh = self.create_bmesh_from_func_data( 487 | func_data, mesh_label, enable_import_scale=True 488 | ) 489 | # print(pre_line + "create_bmesh_from_func_data: ", bmesh) 490 | print( 491 | pre_line + "set auto_smooth: ({}) '{}°'" 492 | "".format( 493 | self.config["auto_smooth_use"], 494 | math.degrees(self.config["auto_smooth_angle"]), 495 | ) 496 | ) 497 | bmesh.use_auto_smooth = self.config["auto_smooth_use"] 498 | bmesh.auto_smooth_angle = self.config["auto_smooth_angle"] 499 | if self.config["auto_smooth_use"]: 500 | for f in bmesh.polygons: 501 | f.use_smooth = True 502 | if mesh_label not in self.imported_obj_names: 503 | self.imported_obj_names.append(mesh_label) 504 | # return (bmesh, bmesh_old_name) 505 | func_data["pre_line"] = pre_line_orig 506 | return bmesh 507 | 508 | def create_or_update_bobj(self, pre_line, func_data, obj_label, bmesh): 509 | """Create or update bobj.""" 510 | pre_line_orig = func_data["pre_line"] 511 | print(pre_line_orig + "create_or_update_bobj") 512 | pre_line = pre_line_orig + " " 513 | func_data["pre_line"] = pre_line 514 | bobj = None 515 | is_new = False 516 | bobj_import = True 517 | # locate existing object (object with same name) 518 | if obj_label in bpy.data.objects: 519 | bobj = bpy.data.objects[obj_label] 520 | print(pre_line + "found bobj!") 521 | bobj_import = False 522 | if obj_label not in self.imported_obj_names and self.config["update"]: 523 | print( 524 | pre_line + "Replacing existing object mesh: {}" "".format(obj_label) 525 | ) 526 | # update only the mesh of existing object. 527 | # print(self.imported_obj_names) 528 | if len(bmesh.materials) <= 0: 529 | # TODO: fix this!! 530 | # correctly handle multimaterials 531 | # copy old materials to new mesh: 532 | for mat in bobj.data.materials: 533 | bmesh.materials.append(mat) 534 | bobj.data = bmesh 535 | # self.handle_material_update(func_data, bobj) 536 | bobj_import = False 537 | # create bobj 538 | if bobj_import: 539 | # print( 540 | # pre_line + 541 | # "create_bobj_from_bmesh: '{}'" 542 | # "".format(obj_label) 543 | # ) 544 | bobj = self.create_bobj_from_bmesh(func_data, obj_label, bmesh) 545 | is_new = True 546 | # print( 547 | # pre_line + 548 | # "created new bobj: {}" 549 | # "".format(bobj) 550 | # ) 551 | func_data["pre_line"] = pre_line_orig 552 | return (is_new, bobj) 553 | 554 | def add_or_update_blender_obj(self, func_data): 555 | """Create or update object with mesh and material data.""" 556 | """ 557 | What should happen? 558 | check if we have the mesh already 559 | if not create 560 | check if we have the object already 561 | if not create it 562 | """ 563 | pre_line_orig = func_data["pre_line"] 564 | print(pre_line_orig + "add_or_update_blender_obj") 565 | pre_line = pre_line_orig + " " 566 | func_data["pre_line"] = pre_line 567 | 568 | obj_label = self.get_obj_label(func_data["obj"]) 569 | mesh_label = obj_label 570 | if func_data["is_link"] and func_data["obj_label"]: 571 | obj_label = func_data["obj_label"] 572 | 573 | # print(pre_line + "obj_label", obj_label) 574 | # print(pre_line + "mesh_label", mesh_label) 575 | # print(pre_line + "obj", self.format_obj(func_data["obj"])) 576 | 577 | bmesh = self.create_or_get_bmesh(pre_line, func_data, mesh_label) 578 | 579 | is_new, bobj = self.create_or_update_bobj(pre_line, func_data, obj_label, bmesh) 580 | 581 | if self.config["update"] or is_new: 582 | # if func_data["obj"].isDerivedFrom("Part::Feature"): 583 | # print(pre_line + " obj isDerivedFrom Part::Feature") 584 | # if func_data["obj"].isDerivedFrom("App::Part"): 585 | # print(pre_line + " obj isDerivedFrom App::Part") 586 | # if func_data["parent_obj"].isDerivedFrom("Part::Feature"): 587 | # print(pre_line + "parent_obj isDerivedFrom Part::Feature") 588 | # if func_data["parent_obj"].isDerivedFrom("App::Part"): 589 | # print(pre_line + "parent_obj isDerivedFrom App::Part") 590 | 591 | if func_data["is_link"]: 592 | # print(pre_line + "is link") 593 | if func_data["obj"].isDerivedFrom("Part::Feature") and func_data[ 594 | "parent_obj" 595 | ].isDerivedFrom("App::Part"): 596 | # print( 597 | # pre_line + 598 | # "is_link " 599 | # "&& obj is Part::Feature " 600 | # "&& parent_obj is App::Part " 601 | # ) 602 | self.handle_placement(pre_line, func_data["obj"], bobj) 603 | else: 604 | # print(pre_line + "is not link") 605 | self.handle_placement(pre_line, func_data["obj"], bobj) 606 | 607 | if bobj.name not in self.imported_obj_names: 608 | self.imported_obj_names.append(bobj.name) 609 | func_data["bobj"] = bobj 610 | func_data["pre_line"] = pre_line_orig 611 | 612 | def sub_collection_add_or_update(self, func_data, collection_label): 613 | """Part-Collection handle add or update.""" 614 | print( 615 | func_data["pre_line"] 616 | + "sub_collection_add_or_update: '{}'".format(collection_label) 617 | ) 618 | temp_collection = None 619 | if self.config["update"]: 620 | if collection_label in bpy.data.collections: 621 | temp_collection = bpy.data.collections[collection_label] 622 | else: 623 | helper.rename_old_data(bpy.data.collections, collection_label) 624 | 625 | if not temp_collection: 626 | # create new 627 | temp_collection = bpy.data.collections.new(collection_label) 628 | func_data["collection"].children.link(temp_collection) 629 | print( 630 | func_data["pre_line"] + "'{}' add to '{}' " 631 | "".format(func_data["bobj"], func_data["collection"],) 632 | ) 633 | else: 634 | # bpy.context.scene.collection.children.link(self.fcstd_collection) 635 | pass 636 | 637 | # update func_data links 638 | func_data["collection_parent"] = func_data["collection"] 639 | func_data["collection"] = temp_collection 640 | 641 | def set_obj_parent_and_collection(self, pre_line, func_data, bobj): 642 | """Set Object parent and collection.""" 643 | bobj.parent = func_data["parent_bobj"] 644 | print( 645 | pre_line + "'{}' set parent to '{}' " 646 | "".format(bobj, func_data["parent_bobj"]) 647 | ) 648 | 649 | # add object to current collection 650 | collection = func_data["collection"] 651 | if not collection: 652 | collection = self.fcstd_collection 653 | if bobj.name not in collection.objects: 654 | collection.objects.link(bobj) 655 | # print( 656 | # pre_line + 657 | # "'{}' add to '{}' " 658 | # "".format(bobj, collection) 659 | # ) 660 | 661 | def parent_empty_add_or_update(self, func_data, empty_label): 662 | """Parent Empty handle add or update.""" 663 | print( 664 | func_data["pre_line"] 665 | + "parent_empty_add_or_update: '{}'".format(empty_label) 666 | ) 667 | pre_line = func_data["pre_line"] + " → " 668 | empty_bobj = None 669 | 670 | obj = func_data["obj"] 671 | 672 | print( 673 | pre_line + "current parent_obj ", self.format_obj(func_data["parent_obj"]) 674 | ) 675 | 676 | if empty_label in bpy.data.objects: 677 | # print( 678 | # pre_line + 679 | # "'{}' already in objects list.".format(empty_label) 680 | # ) 681 | if self.config["update"]: 682 | empty_bobj = bpy.data.objects[empty_label] 683 | # print( 684 | # pre_line + 685 | # "update: '{}'".format(empty_bobj) 686 | # ) 687 | else: 688 | renamed_to = helper.rename_old_data(bpy.data.objects, empty_label) 689 | print(pre_line + "overwrite - renamed to " "'{}'".format(renamed_to)) 690 | 691 | flag_new = False 692 | if empty_bobj is None: 693 | print(pre_line + "create new empty_bobj '{}'".format(empty_label)) 694 | empty_bobj = bpy.data.objects.new(name=empty_label, object_data=None) 695 | empty_bobj.empty_display_size = self.config["scale"] * 10 696 | self.set_obj_parent_and_collection(pre_line, func_data, empty_bobj) 697 | 698 | if self.config["update"] or flag_new: 699 | # set position of empty 700 | if obj: 701 | self.handle_placement( 702 | pre_line, obj, empty_bobj, 703 | ) 704 | # NOT HERE. 705 | # if not func_data["is_link"]: 706 | # self.handle_placement( 707 | # pre_line, 708 | # obj, 709 | # empty_bobj, 710 | # ) 711 | # 712 | # TODO: handle origin things corrrectly... 713 | # origin_bobj 714 | # getLinkedObject() 715 | # >>> doc.Link006.getLinkedObject().Label 716 | # 'Seagull_Double' 717 | # >>> doc.Link003.getLinkedObject().Label 718 | # 'Seagull_A1' 719 | # 720 | # if ( 721 | # not func_data["is_link"] 722 | # ): 723 | # self.handle_placement( 724 | # obj, 725 | # empty_bobj, 726 | # ) 727 | # print( 728 | # pre_line + 729 | # "'{}' set position" 730 | # "".format(empty_bobj) 731 | # # "'{}' set position to '{}'" 732 | # # "".format(empty_bobj, position) 733 | # ) 734 | 735 | # update func_data links 736 | func_data["parent_obj"] = obj 737 | func_data["parent_bobj"] = empty_bobj 738 | return empty_bobj 739 | 740 | def create_collection_instance( 741 | self, func_data, pre_line, obj_label, base_collection 742 | ): 743 | """Create instance of given collection.""" 744 | result_bobj = bpy.data.objects.new(name=obj_label, object_data=None) 745 | result_bobj.instance_collection = base_collection 746 | result_bobj.instance_type = "COLLECTION" 747 | result_bobj.empty_display_size = self.config["scale"] * 10 748 | 749 | # TODO: CHECK where to add this! 750 | if func_data["collection"]: 751 | func_data["collection"].objects.link(result_bobj) 752 | print( 753 | pre_line + "'{}' add to '{}' " 754 | "".format(result_bobj, func_data["collection"]) 755 | ) 756 | # result_bobj.parent = func_data["parent_bobj"] 757 | # result_bobj.parent = parent_obj 758 | if result_bobj.name in bpy.context.scene.collection.objects: 759 | bpy.context.scene.collection.objects.unlink(result_bobj) 760 | 761 | return result_bobj 762 | 763 | def create_link_instance( 764 | self, func_data, pre_line, obj_label, link_target_bobj, link_target_obj 765 | ): 766 | """Create instance of given link_target_bobj.""" 767 | result_bobj = None 768 | if link_target_obj.isDerivedFrom("Part::Feature"): 769 | object_data = None 770 | if link_target_bobj: 771 | object_data = link_target_bobj.data 772 | else: 773 | object_data = bpy.data.meshes.new(name=obj_label + ".temp") 774 | result_bobj = bpy.data.objects.new(name=obj_label, object_data=object_data) 775 | result_bobj.empty_display_size = self.config["scale"] * 10 776 | else: 777 | self.config["report"]( 778 | {"WARNING"}, 779 | (" TODO: create_link_instance " "part handling not implemented yet!!"), 780 | pre_line, 781 | ) 782 | 783 | if link_target_bobj: 784 | result_bobj.scale = link_target_bobj.scale 785 | # check if we need to create link children... 786 | if link_target_obj.children: 787 | self.config["report"]( 788 | {"WARNING"}, 789 | ( 790 | " Warning: create_link_instance " 791 | "children handling not implemented yet!!" 792 | ), 793 | pre_line, 794 | ) 795 | return result_bobj 796 | 797 | def handle__sub_object_import( 798 | self, 799 | *, 800 | func_data, 801 | obj, 802 | pre_line, 803 | parent_obj, 804 | parent_bobj, 805 | is_link_source=False, 806 | ): 807 | """Handle sub object.""" 808 | pre_line_orig = func_data["pre_line"] 809 | print(pre_line_orig + "handle__sub_object_import") 810 | pre_line = pre_line_orig + " " 811 | func_data["pre_line"] = pre_line 812 | link_source = None 813 | obj_label = self.get_obj_label(obj) 814 | # print(pre_line + "obj_label: " + obj_label) 815 | if func_data["is_link"]: 816 | obj_label = self.get_sub_obj_label( 817 | pre_line, 818 | func_data, 819 | # parent_obj, 820 | obj, 821 | ) 822 | print( 823 | pre_line 824 | + "set special link obj_label: " 825 | + b_helper.colors.fg.green 826 | + "'{}'".format(obj_label) 827 | + b_helper.colors.reset 828 | ) 829 | link_source = func_data["link_source"] 830 | if is_link_source: 831 | link_source = obj 832 | # debug output 833 | print(pre_line + ("*" * 42)) 834 | print(pre_line + "obj: " + self.format_obj(obj)) 835 | print(pre_line + "parent_obj: " + self.format_obj(parent_obj)) 836 | print(pre_line + "parent_bobj: {}".format(parent_bobj)) 837 | # # print(pre_line + "func_data[is_link]: {}".format(func_data["is_link"])) 838 | # is_link_color = b_helper.colors.fg.red 839 | # if func_data["is_link"]: 840 | # is_link_color = b_helper.colors.fg.green 841 | # print( 842 | # pre_line 843 | # + "func_data[is_link]: " 844 | # + is_link_color 845 | # + "{}".format(func_data["is_link"]) 846 | # + b_helper.colors.reset 847 | # ) 848 | # is_link_source_color = b_helper.colors.fg.red 849 | # if is_link_source: 850 | # is_link_source_color = b_helper.colors.fg.green 851 | # print( 852 | # pre_line 853 | # + "is_link_source: " 854 | # + is_link_source_color 855 | # + "{}".format(is_link_source) 856 | # + b_helper.colors.reset 857 | # ) 858 | # print( 859 | # pre_line 860 | # + "func_data[link_source]: " 861 | # + self.format_obj(func_data["link_source"]) 862 | # ) 863 | # print( 864 | # pre_line 865 | # + "link_source: " 866 | # + self.format_obj(link_source) 867 | # ) 868 | # print(pre_line + ("*"*42)) 869 | # prepare import 870 | func_data_new = self.create_func_data() 871 | func_data_new["obj"] = obj 872 | func_data_new["obj_label"] = obj_label 873 | func_data_new["collection"] = func_data["collection"] 874 | func_data_new["collection_parent"] = func_data["collection_parent"] 875 | func_data_new["parent_obj"] = parent_obj 876 | func_data_new["parent_bobj"] = parent_bobj 877 | func_data_new["is_link"] = func_data["is_link"] 878 | func_data_new["link_source"] = link_source 879 | print(pre_line + "import_obj ...") 880 | self.import_obj( 881 | func_data=func_data_new, pre_line=pre_line, 882 | ) 883 | func_data["pre_line"] = pre_line_orig 884 | 885 | def handle__sub_objects( 886 | self, 887 | func_data, 888 | sub_objects, 889 | pre_line, 890 | parent_obj, 891 | parent_bobj, 892 | include_only_visible=True, 893 | is_link_source=False, 894 | ): 895 | """Handle sub object.""" 896 | # │─ ┌─ └─ ├─ ╞═ ╘═╒═ 897 | # ║═ ╔═ ╚═ ╠═ ╟─ 898 | # ┃━ ┏━ ┗━ ┣━ ┠─ 899 | pre_line_start = pre_line + "╔════ " 900 | pre_line_sub_special = pre_line + "╠════ " 901 | pre_line_sub = pre_line + "╠═ " 902 | pre_line_follow = pre_line + "║ " 903 | pre_line_end = pre_line + "╚════ " 904 | 905 | pre_line_orig = func_data["pre_line"] 906 | pre_line = pre_line_follow 907 | func_data["pre_line"] = pre_line 908 | print( 909 | pre_line_start 910 | + "handle__sub_objects" 911 | # + " - sub_objects '{}'" 912 | # + "".format(sub_objects) 913 | ) 914 | sub_filter_visible = False 915 | if not isinstance(include_only_visible, list): 916 | # convert True or False to list 917 | include_only_visible = [None] * len(sub_objects) 918 | sub_filter_visible = True 919 | # print( 920 | # pre_line + 921 | # "include_only_visible '{}'" 922 | # "".format(include_only_visible) 923 | # ) 924 | # print( 925 | # pre_line + 926 | # "sub_objects '{}'" 927 | # "".format(sub_objects) 928 | # ) 929 | # print( 930 | # pre_line + 931 | # "is_link_source '{}'" 932 | # "".format(is_link_source) 933 | # ) 934 | print(pre_line + "parent_obj: " + self.format_obj(parent_obj)) 935 | print(pre_line + "parent_bobj: {}".format(parent_bobj)) 936 | sub_objects = fc_helper.filtered_objects( 937 | sub_objects, include_only_visible=sub_filter_visible 938 | ) 939 | self.config["report"]( 940 | {"INFO"}, 941 | ( 942 | b_helper.colors.bold 943 | + b_helper.colors.fg.purple 944 | + "Import {} Recusive:".format(len(sub_objects)) 945 | + b_helper.colors.reset 946 | ), 947 | pre_line=pre_line_sub_special, 948 | ) 949 | # is_link_source = False 950 | # # if func_data["is_link"] and len(sub_objects) > 1: 951 | # if len(sub_objects) > 1: 952 | # is_link_source = True 953 | for index, obj in enumerate(sub_objects): 954 | if self.check_obj_visibility_with_skiphidden( 955 | obj, include_only_visible[index] 956 | ): 957 | self.print_obj(obj, pre_line_sub) 958 | self.handle__sub_object_import( 959 | func_data=func_data, 960 | obj=obj, 961 | pre_line=pre_line_follow, 962 | parent_obj=parent_obj, 963 | parent_bobj=parent_bobj, 964 | is_link_source=is_link_source, 965 | ) 966 | else: 967 | self.print_obj( 968 | obj=obj, 969 | pre_line=pre_line_sub, 970 | post_line=( 971 | b_helper.colors.fg.darkgrey 972 | + " (skipping - hidden)" 973 | + b_helper.colors.reset 974 | ), 975 | ) 976 | func_data["pre_line"] = pre_line_orig 977 | 978 | if func_data["bobj"] is None: 979 | func_data["bobj"] = parent_bobj 980 | 981 | self.config["report"]( 982 | {"INFO"}, 983 | ( 984 | b_helper.colors.bold 985 | + b_helper.colors.fg.purple 986 | + "done." 987 | + b_helper.colors.reset 988 | ), 989 | pre_line=pre_line_end, 990 | ) 991 | 992 | def handle__object_with_sub_objects( 993 | self, func_data, sub_objects, include_only_visible=True, is_link_source=False, 994 | ): 995 | """Handle sub objects.""" 996 | pre_line = func_data["pre_line"] 997 | parent_obj = func_data["obj"] 998 | parent_label = self.get_obj_label(parent_obj) 999 | if func_data["is_link"] and func_data["obj_label"]: 1000 | parent_label = func_data["obj_label"] 1001 | print(pre_line + "handle__object_with_sub_objects '{}'".format(parent_label)) 1002 | # print(pre_line + "is_link_source '{}'".format(is_link_source)) 1003 | # pre_line += "→ " 1004 | 1005 | # print(pre_line + "force update parent_bobj to match parent_obj") 1006 | # p_label = self.get_obj_label(func_data["parent_obj"]) 1007 | # if ( 1008 | # func_data["parent_obj"] 1009 | # and p_label in bpy.data.objects 1010 | # ): 1011 | # func_data["parent_bobj"] = bpy.data.objects[p_label] 1012 | 1013 | self.print_obj( 1014 | func_data["parent_obj"], pre_line=pre_line + "# func_data[parent_obj]", 1015 | ) 1016 | print(pre_line + "# func_data[parent_bobj]", func_data["parent_bobj"]) 1017 | 1018 | self.parent_empty_add_or_update(func_data, parent_label) 1019 | parent_bobj = func_data["parent_bobj"] 1020 | print(pre_line + "fresh created parent_bobj ", parent_bobj) 1021 | 1022 | if len(sub_objects) > 0: 1023 | self.handle__sub_objects( 1024 | func_data, 1025 | sub_objects, 1026 | pre_line, 1027 | parent_obj, 1028 | parent_bobj, 1029 | include_only_visible=include_only_visible, 1030 | is_link_source=is_link_source, 1031 | ) 1032 | else: 1033 | self.config["report"]( 1034 | {"INFO"}, 1035 | (b_helper.colors.fg.darkgrey + "→ no childs." + b_helper.colors.reset), 1036 | pre_line=pre_line, 1037 | ) 1038 | 1039 | # ########################################## 1040 | # Arrays and similar 1041 | def handle__ObjectWithElementList(self, func_data, is_link_source=False): 1042 | """Handle Part::Feature objects.""" 1043 | pre_line_orig = func_data["pre_line"] 1044 | print(pre_line_orig + "handle__ObjectWithElementList") 1045 | pre_line = pre_line_orig + " " 1046 | func_data["pre_line"] = pre_line 1047 | # fc_helper.print_objects( 1048 | # func_data["obj"].ElementList, 1049 | # pre_line=pre_line 1050 | # ) 1051 | include_only_visible = [*func_data["obj"].VisibilityList] 1052 | self.handle__object_with_sub_objects( 1053 | func_data, 1054 | func_data["obj"].ElementList, 1055 | include_only_visible=include_only_visible, 1056 | is_link_source=is_link_source, 1057 | ) 1058 | func_data["pre_line"] = pre_line_orig 1059 | 1060 | # Part::FeaturePhython 1061 | def handle__PartFeaturePython_Array(self, func_data): 1062 | """Handle Part::Feature objects.""" 1063 | pre_line_orig = func_data["pre_line"] 1064 | print( 1065 | pre_line_orig + "handle__PartFeaturePython_Array", 1066 | self.format_obj(func_data["obj"]), 1067 | ) 1068 | pre_line = pre_line_orig + " " 1069 | func_data["pre_line"] = pre_line 1070 | pre_line = func_data["pre_line"] 1071 | # print( 1072 | # pre_line + "ElementList:", 1073 | # func_data["obj"].ElementList 1074 | # ) 1075 | # print(pre_line + "Count:", func_data["obj"].Count) 1076 | # print(pre_line + "ExpandArray:", func_data["obj"].ExpandArray) 1077 | # print(pre_line + "expand Array") 1078 | # TODO: this currently has only any effect in the GUI 1079 | func_data["obj"].ExpandArray = True 1080 | self.doc.recompute() 1081 | # print(pre_line + "ExpandArray:", func_data["obj"].ExpandArray) 1082 | print(pre_line + "ElementList:", func_data["obj"].ElementList) 1083 | # print( 1084 | # pre_line 1085 | # + "call handle__ObjectWithElementList with " 1086 | # + b_helper.colors.fg.orange 1087 | # + "is_link_source=True" 1088 | # + b_helper.colors.reset 1089 | # + ".." 1090 | # ) 1091 | self.handle__ObjectWithElementList(func_data, is_link_source=True) 1092 | func_data["pre_line"] = pre_line_orig 1093 | 1094 | def handle__PartFeaturePython_ArchWithHostChilds(self, func_data): 1095 | """Handle Part::Feature Arch objects with HostsChilds.""" 1096 | pre_line_orig = func_data["pre_line"] 1097 | print( 1098 | pre_line_orig + "handle__PartFeaturePython_ArchWithHostChilds", 1099 | self.format_obj(func_data["obj"]), 1100 | ) 1101 | pre_line = pre_line_orig + " " 1102 | func_data["pre_line"] = pre_line 1103 | pre_line = func_data["pre_line"] 1104 | obj = func_data["obj"] 1105 | # import the part itself 1106 | self.handle__PartFeature(func_data) 1107 | # handle childs 1108 | original_parent = func_data["parent_bobj"] 1109 | obj_childs = fc_helper.object_get_HostChilds(obj) 1110 | # print(pre_line + "obj_childs:", obj_childs) 1111 | # print(pre_line + "len(obj_childs):", len(obj_childs)) 1112 | self.handle__object_with_sub_objects(func_data, obj_childs) 1113 | # restor 1114 | func_data["parent_bobj"] = original_parent 1115 | func_data["pre_line"] = pre_line_orig 1116 | 1117 | def handle__PartFeaturePython(self, func_data, pre_line=""): 1118 | """Handle Part::FeaturePython objects.""" 1119 | obj = func_data["obj"] 1120 | if hasattr(obj, "ExpandArray") and hasattr(obj, "ElementList"): 1121 | self.handle__PartFeaturePython_Array(func_data) 1122 | elif hasattr(obj, "ArrayType"): 1123 | self.config["report"]( 1124 | {"WARNING"}, 1125 | ( 1126 | "Unable to load '{}' ('{}') of type '{}'. " 1127 | "(Type Not implemented yet)." 1128 | "".format(obj.Label, obj.Name, obj.TypeId) 1129 | ), 1130 | pre_line, 1131 | ) 1132 | elif len(fc_helper.object_get_HostChilds(obj)) > 0: 1133 | # Arch Workbench - ArchComponent 1134 | self.handle__PartFeaturePython_ArchWithHostChilds(func_data) 1135 | elif hasattr(obj, "Hosts"): 1136 | # Arch Workbench - Childs 1137 | self.handle__PartFeature(func_data) 1138 | 1139 | # self.handle__object_hosts(func_data) 1140 | # not needed anymore - as the main import now handles the parent-child relationship 1141 | else: 1142 | self.config["report"]( 1143 | {"WARNING"}, 1144 | ( 1145 | "try to load '{}' ('{}') of type '{}' as normal Part::Feature. " 1146 | "no special implementation for Sub-Type of 'Part::FeaturePython' found." 1147 | "".format(obj.Label, obj.Name, obj.TypeId) 1148 | ), 1149 | pre_line, 1150 | ) 1151 | self.handle__PartFeature(func_data) 1152 | 1153 | # App::Part 1154 | def handle__AppPart(self, func_data): 1155 | """Handle App:Part type.""" 1156 | # pre_line = func_data["pre_line"] 1157 | self.handle__object_with_sub_objects(func_data, func_data["obj"].Group) 1158 | 1159 | # App::Link* 1160 | def add_or_update_collection_instance( 1161 | self, *, func_data, obj, obj_label, instance_target_label, 1162 | ): 1163 | """Add or update collection instance object.""" 1164 | pre_line_orig = func_data["pre_line"] 1165 | pre_line = pre_line_orig 1166 | # │─ ┌─ └─ ├─ ╞═ ╘═╒═ 1167 | # ║═ ╔═ ╚═ ╠═ ╟─ 1168 | # ┃━ ┏━ ┗━ ┣━ ┠─ 1169 | pre_line_start = pre_line_orig + "┌ " 1170 | # pre_line_sub = pre_line_orig + "├─ " 1171 | pre_line_follow = pre_line_orig + "│ " 1172 | pre_line_end = pre_line_orig + "└────────── " 1173 | print( 1174 | pre_line_start + "add_or_update_collection_instance '{}'" 1175 | "".format(obj_label) 1176 | ) 1177 | func_data["pre_line"] = pre_line_follow 1178 | # pre_line = pre_line_sub 1179 | pre_line = pre_line_follow 1180 | 1181 | print(pre_line + "obj_label '{}'".format(obj_label)) 1182 | print(pre_line + "instance_target_label '{}'".format(instance_target_label)) 1183 | print( 1184 | pre_line + "func_data[collection] '{}'" "".format(func_data["collection"]) 1185 | ) 1186 | 1187 | base_collection = None 1188 | bobj = None 1189 | if instance_target_label in bpy.data.collections: 1190 | base_collection = bpy.data.collections[instance_target_label] 1191 | flag_new = False 1192 | if obj_label in bpy.data.objects: 1193 | bobj = bpy.data.objects[obj_label] 1194 | else: 1195 | bobj = self.create_collection_instance( 1196 | func_data, pre_line_follow, obj_label, base_collection 1197 | ) 1198 | flag_new = True 1199 | # print( 1200 | # pre_line + 1201 | # "bobj '{}'; new:{}" 1202 | # "".format(bobj, flag_new) 1203 | # ) 1204 | if self.config["update"] or flag_new: 1205 | self.set_obj_parent_and_collection(pre_line_follow, func_data, bobj) 1206 | self.handle_placement(pre_line_follow, obj, bobj, enable_scale=True) 1207 | # print( 1208 | # pre_line + " " 1209 | # "bobj '{}' ".format(bobj.location) 1210 | # ) 1211 | else: 1212 | self.config["report"]( 1213 | {"WARNING"}, 1214 | ( 1215 | "Warning: can't add or update instance. " 1216 | "'{}' collection not found." 1217 | "".format(instance_target_label) 1218 | ), 1219 | pre_line, 1220 | ) 1221 | # return False 1222 | print(pre_line_end + "") 1223 | func_data["pre_line"] = pre_line_orig 1224 | 1225 | def add_or_update_link_instance( 1226 | self, *, func_data, obj, obj_label, link_target_obj, link_target_label, 1227 | ): 1228 | """Add or update link instance object.""" 1229 | pre_line_orig = func_data["pre_line"] 1230 | pre_line = pre_line_orig 1231 | # │─ ┌─ └─ ├─ ╞═ ╘═╒═ 1232 | # ║═ ╔═ ╚═ ╠═ ╟─ 1233 | # ┃━ ┏━ ┗━ ┣━ ┠─ 1234 | pre_line_start = pre_line_orig + "┌ " 1235 | # pre_line_sub = pre_line_orig + "├─ " 1236 | pre_line_follow = pre_line_orig + "│ " 1237 | pre_line_end = pre_line_orig + "└────────── " 1238 | print(pre_line_start + "add_or_update_link_instance '{}'" "".format(obj_label)) 1239 | func_data["pre_line"] = pre_line_follow 1240 | # pre_line = pre_line_sub 1241 | pre_line = pre_line_follow 1242 | 1243 | print(pre_line + "obj_label '{}'".format(obj_label)) 1244 | print(pre_line + "link_target_label '{}'".format(link_target_label)) 1245 | 1246 | link_target_bobj = None 1247 | bobj = None 1248 | if link_target_label in bpy.data.objects: 1249 | link_target_bobj = bpy.data.objects[link_target_label] 1250 | print(pre_line + "# link_target_bobj ", link_target_bobj) 1251 | else: 1252 | self.config["report"]( 1253 | {"WARNING"}, 1254 | ( 1255 | "Warning: can't add or update linnk instance. " 1256 | "'{}' link_target not found." 1257 | "".format(link_target_label) 1258 | ), 1259 | pre_line, 1260 | ) 1261 | # return False 1262 | flag_new = False 1263 | if obj_label in bpy.data.objects: 1264 | bobj = bpy.data.objects[obj_label] 1265 | print(pre_line + "# bobj already here: ", bobj) 1266 | else: 1267 | bobj = self.create_link_instance( 1268 | func_data, pre_line_follow, obj_label, link_target_bobj, link_target_obj 1269 | ) 1270 | print(pre_line + "# created new bobj: ", bobj) 1271 | flag_new = True 1272 | # print( 1273 | # pre_line + 1274 | # "bobj '{}'; new:{}" 1275 | # "".format(bobj, flag_new) 1276 | # ) 1277 | if self.config["update"] or flag_new: 1278 | # if func_data["parent_bobj"] is None: 1279 | # func_data["parent_bobj"] = link_target_bobj 1280 | # print( 1281 | # pre_line + 1282 | # "'{}' try to set parent to '{}' " 1283 | # "".format(bobj, func_data["parent_bobj"]) 1284 | # ) 1285 | # self.set_obj_parent_and_collection( 1286 | # pre_line_follow, 1287 | # func_data, 1288 | # bobj 1289 | # ) 1290 | self.handle_placement(pre_line_follow, obj, bobj, enable_scale=True) 1291 | # print( 1292 | # pre_line + " " 1293 | # "bobj '{}' ".format(bobj.location) 1294 | # ) 1295 | print(pre_line + "# bobj.data: ", bobj.data) 1296 | if bobj.data: 1297 | if bobj.data.name != link_target_label: 1298 | if link_target_label in bpy.data.meshes: 1299 | print( 1300 | pre_line 1301 | + "update / relink '{}' to original link target '{}'" 1302 | "".format(obj_label, link_target_label) 1303 | ) 1304 | old_mesh = bobj.data 1305 | bobj.data = bpy.data.meshes[link_target_label] 1306 | # clean up temporary mesh 1307 | if old_mesh.users == 0: 1308 | bpy.data.meshes.remove(old_mesh) 1309 | else: 1310 | print( 1311 | pre_line + "→ link_target_label not in bpy.data.meshes " 1312 | "Something wired going on.... " 1313 | "it seems to working..." 1314 | "TODO: maybe CHECK" 1315 | ) 1316 | # else: 1317 | # print( 1318 | # pre_line + 1319 | # "→ bobj.data.name '{}' == link_target_label '{}' " 1320 | # "".format(bobj.data.name, link_target_label) 1321 | # ) 1322 | else: 1323 | print(pre_line + "→ bobj.data == None " "→ maybe this is a Empty.") 1324 | 1325 | func_data["bobj"] = bobj 1326 | func_data["update_tree"] = True 1327 | 1328 | print(pre_line_end + "") 1329 | func_data["pre_line"] = pre_line_orig 1330 | 1331 | def add_or_update_link_target( 1332 | self, *, func_data, obj, obj_label, obj_linkedobj, obj_linkedobj_label, 1333 | ): 1334 | """Add or update link target object.""" 1335 | pre_line = func_data["pre_line"] 1336 | # print( 1337 | # pre_line + 1338 | # "$ add_or_update_link_target: '{}'" 1339 | # "".format( 1340 | # obj_linkedobj_label, 1341 | # ) 1342 | # ) 1343 | # print( 1344 | # pre_line + 1345 | # "$ obj: '{}' '{}' parent: '{}'" 1346 | # "".format( 1347 | # obj, 1348 | # obj.Label, 1349 | # obj.getParentGeoFeatureGroup() 1350 | # ) 1351 | # ) 1352 | 1353 | # print( 1354 | # pre_line + 1355 | # "self.imported_obj_names ", 1356 | # self.imported_obj_names 1357 | # ) 1358 | 1359 | if obj_linkedobj_label in bpy.data.objects or ( 1360 | obj_linkedobj_label in self.imported_obj_names 1361 | ): 1362 | print( 1363 | pre_line + "→ already imported/updated '{}'." 1364 | "".format(obj_linkedobj_label) 1365 | ) 1366 | else: 1367 | self.print_obj( 1368 | obj, 1369 | pre_line=pre_line + "# ", 1370 | post_line=" → import Link Target {}.".format( 1371 | self.format_obj(obj_linkedobj) 1372 | ), 1373 | end="", 1374 | ) 1375 | 1376 | # self.print_obj( 1377 | # obj_linkedobj, 1378 | # pre_line=pre_line + "# ", 1379 | # post_line="" 1380 | # ) 1381 | 1382 | # self.print_obj( 1383 | # func_data["parent_obj"], 1384 | # pre_line=pre_line + "# func_data[parent_obj]", 1385 | # ) 1386 | # print( 1387 | # pre_line + "# func_data[parent_bobj]", 1388 | # func_data["parent_bobj"] 1389 | # ) 1390 | 1391 | # if obj_linkedobj_label in bpy.data.objects: 1392 | # self.config["report"]({'INFO'}, ( 1393 | # "skipping import. '{}' already in objects list." 1394 | # "".format(obj_linkedobj_label) 1395 | # ), pre_line) 1396 | # else: 1397 | # set collection to link_target 1398 | # this way the imports get definitly added to the scene. 1399 | # func_data["collection"] = self.link_targets 1400 | print(pre_line + "§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§") 1401 | func_data_obj_linked = self.create_func_data() 1402 | func_data_obj_linked["obj"] = obj_linkedobj 1403 | func_data_obj_linked["collection"] = self.link_targets 1404 | func_data_obj_linked["collection_parent"] = None 1405 | func_data_obj_linked["parent_obj"] = obj 1406 | func_data_obj_linked["parent_bobj"] = None 1407 | func_data_obj_linked = self.import_obj( 1408 | func_data=func_data_obj_linked, pre_line=pre_line + " ", 1409 | ) 1410 | print(pre_line + "§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§") 1411 | bobj = func_data_obj_linked["bobj"] 1412 | # print( 1413 | # pre_line + 1414 | # "func_data_obj_linked '{}' " 1415 | # "".format(func_data_obj_linked) 1416 | # ) 1417 | # pprint.pprint(func_data_obj_linked) 1418 | 1419 | # fix parent linking 1420 | # parent_obj = obj_linked.getParentGeoFeatureGroup() 1421 | # parent_label = self.get_obj_label(parent_obj) 1422 | # if parent_label: 1423 | # parent_bobj = bpy.data.objects[parent_label] 1424 | # if parent_bobj: 1425 | # bobj.parent = parent_bobj 1426 | # print( 1427 | # pre_line + 1428 | # "'{}' set parent to '{}' " 1429 | # "".format(bobj, parent_bobj) 1430 | # ) 1431 | # this has no parent as we use only the raw obj. 1432 | self.print_obj(func_data_obj_linked["obj"], pre_line + "$ used obj: ") 1433 | print(pre_line + "$ created bobj: ", bobj) 1434 | print(pre_line + "$ bobj.parent: ", bobj.parent) 1435 | print(pre_line + "$ func_data[parent_bobj]: ", func_data["parent_bobj"]) 1436 | # bobj.parent = None 1437 | self.reset_placement_position(bobj) 1438 | 1439 | # print( 1440 | # pre_line + "$ parent_bobj: ", 1441 | # func_data_obj_linked["parent_bobj"] 1442 | # ) 1443 | # print( 1444 | # pre_line + "$ collection: ", 1445 | # func_data_obj_linked["collection"] 1446 | # ) 1447 | # print( 1448 | # pre_line + "$ collection_parent: ", 1449 | # func_data_obj_linked["collection_parent"] 1450 | # ) 1451 | 1452 | # created collection for new link target 1453 | func_data_obj_linked["collection"] = self.link_targets 1454 | self.sub_collection_add_or_update(func_data_obj_linked, obj_linkedobj_label) 1455 | # self.parent_empty_add_or_update( 1456 | # func_data_obj_linked, obj_linkedobj_label) 1457 | # add new object to collection. 1458 | func_data_obj_linked["collection"].objects.link(bobj) 1459 | print( 1460 | pre_line + "'{}' add to '{}' " 1461 | "".format(bobj, func_data_obj_linked["collection"]) 1462 | ) 1463 | 1464 | def handle__AppLink(self, func_data): 1465 | """Handle App::Link objects.""" 1466 | pre_line_orig = func_data["pre_line"] 1467 | pre_line = pre_line_orig 1468 | # │─ ┌─ └─ ├─ ╞═ ╘═╒═ 1469 | # ║═ ╔═ ╚═ ╠═ ╟─ 1470 | # ┃━ ┏━ ┗━ ┣━ ┠─ 1471 | pre_line_start = pre_line_orig + "┌ " 1472 | # pre_line_sub = pre_line_orig + "├─ " 1473 | pre_line_follow = pre_line_orig + "│ " 1474 | pre_line_end = pre_line_orig + "└────────── " 1475 | print(pre_line_start + "handle__AppLink") 1476 | func_data["pre_line"] = pre_line_follow 1477 | # pre_line = pre_line_sub 1478 | pre_line = pre_line_follow 1479 | 1480 | obj = func_data["obj"] 1481 | obj_linkedobj = func_data["obj"].LinkedObject 1482 | if isinstance(obj_linkedobj, tuple): 1483 | obj_linkedobj = obj_linkedobj[0] 1484 | # print(pre_line + "obj_linkedobj :", obj_linkedobj) 1485 | # self.config["report"]({'WARNING'}, ( 1486 | # "'{}' ('{s}') of type '{}': " 1487 | # "".format(obj.Label, obj.Name, obj.TypeId) 1488 | # ), pre_line) 1489 | # self.config["report"]({'WARNING'}, ( 1490 | # " Warning: App::Link handling is highly experimental!!" 1491 | # ), pre_line) 1492 | obj_label = self.get_obj_label(obj) 1493 | if func_data["is_link"] and func_data["obj_label"]: 1494 | obj_label = func_data["obj_label"] 1495 | # obj_linkedobj_label = self.get_obj_linkedobj_label(obj) 1496 | # obj_linked_label = self.get_obj_label(obj_linkedobj) 1497 | 1498 | # print(pre_line + "obj_label:", obj_label) 1499 | # print(pre_line + "obj_linkedobj_label:", obj_linkedobj_label) 1500 | # print(pre_line + "obj_linked_label:", obj_linked_label) 1501 | # fc_helper.print_obj( 1502 | # obj, 1503 | # pre_line=pre_line + "obj : ") 1504 | # fc_helper.print_obj( 1505 | # obj_linkedobj, 1506 | # pre_line=pre_line + "obj_linkedobj: ") 1507 | # if hasattr(obj_linkedobj, "LinkedObject"): 1508 | # fc_helper.print_obj(obj_linkedobj.LinkedObject, pre_line=pre_line) 1509 | 1510 | if obj_linkedobj: 1511 | orig_is_link = func_data["is_link"] 1512 | func_data["is_link"] = True 1513 | 1514 | if hasattr(obj, "ElementList") and len(obj.ElementList) > 0: 1515 | print(pre_line + "ElementList > 0") 1516 | self.handle__ObjectWithElementList(func_data) 1517 | else: 1518 | print(pre_line + "Single Element → fake list") 1519 | # if target is of Body type get real link target 1520 | # this excludes the link → link → link chain... 1521 | # try: 1522 | # obj_linkedobj.getLinkedObject().isDerivedFrom("Part::Feature") 1523 | # except Exception as e: 1524 | # print(pre_line + "obj_linkedobj error:", e) 1525 | # else: 1526 | # print(pre_line + "use recusive inner target") 1527 | # obj_linkedobj = obj_linkedobj.getLinkedObject() 1528 | if obj_linkedobj.getLinkedObject().isDerivedFrom("Part::Feature"): 1529 | print(pre_line + "use recusive inner target") 1530 | obj_linkedobj = obj_linkedobj.getLinkedObject() 1531 | self.handle__object_with_sub_objects( 1532 | func_data, [obj_linkedobj], include_only_visible=[True] 1533 | ) 1534 | # set back to original 1535 | func_data["is_link"] = orig_is_link 1536 | else: 1537 | self.config["report"]( 1538 | {"WARNING"}, 1539 | ("Warning: '{}' LinkedObject is NONE → skipping." "".format(obj_label)), 1540 | pre_line, 1541 | ) 1542 | print(pre_line_end + "") 1543 | func_data["pre_line"] = pre_line_orig 1544 | 1545 | def handle__AppLinkElement(self, func_data, obj_linkedobj=None): 1546 | """Handle App::LinkElement objects.""" 1547 | pre_line_orig = func_data["pre_line"] 1548 | 1549 | pre_line = pre_line_orig 1550 | # │─ ┌─ └─ ├─ ╞═ ╘═╒═ 1551 | # ║═ ╔═ ╚═ ╠═ ╟─ 1552 | # ┃━ ┏━ ┗━ ┣━ ┠─ 1553 | pre_line_start = pre_line_orig + "┌ " 1554 | # pre_line_sub = pre_line_orig + "├─ " 1555 | pre_line_follow = pre_line_orig + "│ " 1556 | pre_line_end = pre_line_orig + "└────────── " 1557 | print(pre_line_start + "handle__AppLinkElement") 1558 | func_data["pre_line"] = pre_line_follow 1559 | # pre_line = pre_line_sub 1560 | pre_line = pre_line_follow 1561 | 1562 | obj = func_data["obj"] 1563 | if obj_linkedobj is None: 1564 | obj_linkedobj = func_data["obj"].LinkedObject 1565 | 1566 | # if hasattr(obj_linkedobj, "LinkedObject"): 1567 | # # if we have Arrays they have a intermediet link object.. 1568 | # # we skip this.. 1569 | # obj_linkedobj = obj_linkedobj.LinkedObject 1570 | 1571 | # parent_obj = obj.InList[0] 1572 | parent_obj = func_data["parent_obj"] 1573 | # parent_obj_label = self.get_obj_label(parent_obj) 1574 | # if ( 1575 | # parent_obj and 1576 | # parent_obj_label in bpy.data.objects 1577 | # ): 1578 | # func_data["parent_bobj"] = bpy.data.objects[parent_obj_label] 1579 | print(pre_line + "func_data[parent_bobj]:", func_data["parent_bobj"]) 1580 | 1581 | # obj_label = self.get_obj_combined_label(parent_obj, obj) 1582 | obj_label = self.get_obj_label(obj) 1583 | if func_data["is_link"] and func_data["obj_label"]: 1584 | obj_label = func_data["obj_label"] 1585 | # obj_linkedobj_label = self.get_obj_linkedobj_label(obj) 1586 | obj_linkedobj_label = self.get_obj_label(obj_linkedobj) 1587 | 1588 | # print(pre_line + "collection:", func_data["collection"]) 1589 | # print(pre_line + "parent_obj_label:", parent_obj_label) 1590 | print(pre_line + "obj_label:", obj_label) 1591 | print(pre_line + "obj_linkedobj_label:", obj_linkedobj_label) 1592 | fc_helper.print_obj(parent_obj, pre_line=pre_line + "parent_obj : ") 1593 | fc_helper.print_obj(obj, pre_line=pre_line + "obj : ") 1594 | fc_helper.print_obj(obj_linkedobj, pre_line=pre_line + "obj_linkedobj: ") 1595 | # fc_helper.print_obj(obj_linked.LinkedObject, pre_line=pre_line) 1596 | 1597 | if not self.config["links_as_collectioninstance"]: 1598 | self.add_or_update_link_instance( 1599 | func_data=func_data, 1600 | obj=obj, 1601 | obj_label=obj_label, 1602 | link_target_obj=obj_linkedobj, 1603 | link_target_label=obj_linkedobj_label, 1604 | ) 1605 | 1606 | self.add_or_update_link_target( 1607 | func_data=func_data, 1608 | obj=obj, 1609 | obj_label=obj_label, 1610 | obj_linkedobj=obj_linkedobj, 1611 | obj_linkedobj_label=obj_linkedobj_label, 1612 | ) 1613 | 1614 | if self.config["links_as_collectioninstance"]: 1615 | self.add_or_update_collection_instance( 1616 | func_data=func_data, 1617 | obj=obj, 1618 | obj_label=obj_label, 1619 | instance_target_label=obj_linkedobj_label, 1620 | ) 1621 | else: 1622 | self.add_or_update_link_instance( 1623 | func_data=func_data, 1624 | obj=obj, 1625 | obj_label=obj_label, 1626 | link_target_obj=obj_linkedobj, 1627 | link_target_label=obj_linkedobj_label, 1628 | ) 1629 | print(pre_line_end + "") 1630 | func_data["pre_line"] = pre_line_orig 1631 | 1632 | # ########################################## 1633 | # 'Arch' object types 1634 | def handle__object_hosts(self, func_data): 1635 | """Handle object with hosts attribute (Arch Workbench).""" 1636 | pre_line = func_data["pre_line"] 1637 | obj = func_data["obj"] 1638 | obj_host = obj.Hosts[0] 1639 | obj_label = self.get_obj_label(obj) 1640 | obj_host_label = self.get_obj_label(obj_host) 1641 | print(pre_line + "handle__object_hosts '{}'".format(obj_label)) 1642 | print(pre_line + "obj_host_label '{}'".format(obj_host_label)) 1643 | bobj = func_data["bobj"] 1644 | bobj_host = bpy.data.objects[obj_host_label] 1645 | if bobj_host: 1646 | print(pre_line + "bobj_host '{}'".format(bobj_host)) 1647 | print(pre_line + "bobj_host.parent '{}'".format(bobj_host.parent)) 1648 | # Arch Wall Objects are no collection things - so we need to use the parent of it... 1649 | # in the hope that this works... 1650 | if bobj_host.parent: 1651 | bobj.parent = bobj_host.parent 1652 | else: 1653 | self.config["report"]( 1654 | {"WARNING"}, 1655 | ( 1656 | "Warning: host '{}' has no parrent. can not set parent for '{}' " 1657 | "".format(obj_host_label, obj_label) 1658 | ), 1659 | pre_line, 1660 | ) 1661 | 1662 | # ########################################## 1663 | # 'real' object types 1664 | 1665 | # Part::Feature 1666 | def handle_shape_edge(self, func_data, edge): 1667 | """Handle edges that are not part of a face.""" 1668 | if self.hascurves(edge): 1669 | # TODO use tessellation value 1670 | dv = edge.discretize(9) 1671 | for i in range(len(dv) - 1): 1672 | dv1 = [dv[i].x, dv[i].y, dv[i].z] 1673 | dv2 = [dv[i + 1].x, dv[i + 1].y, dv[i + 1].z] 1674 | if dv1 not in func_data["verts"]: 1675 | func_data["verts"].append(dv1) 1676 | if dv2 not in func_data["verts"]: 1677 | func_data["verts"].append(dv2) 1678 | func_data["edges"].append( 1679 | [func_data["verts"].index(dv1), func_data["verts"].index(dv2)] 1680 | ) 1681 | else: 1682 | e = [] 1683 | for vert in edge.Vertexes: 1684 | # TODO discretize non-linear edges 1685 | v = [vert.X, vert.Y, vert.Z] 1686 | if v not in func_data["verts"]: 1687 | func_data["verts"].append(v) 1688 | e.append(func_data["verts"].index(v)) 1689 | func_data["edges"].append(e) 1690 | 1691 | def convert_face_to_polygon(self, func_data, face, faceedges): 1692 | """Convert face to polygons.""" 1693 | import Part 1694 | 1695 | if ( 1696 | (len(face.Wires) > 1) 1697 | or (not isinstance(face.Surface, Part.Plane)) 1698 | or self.hascurves(face) 1699 | ): 1700 | # face has holes or is curved, so we need to triangulate it 1701 | rawdata = face.tessellate(self.config["tessellation"]) 1702 | for v in rawdata[0]: 1703 | vl = [v.x, v.y, v.z] 1704 | if vl not in func_data["verts"]: 1705 | func_data["verts"].append(vl) 1706 | for f in rawdata[1]: 1707 | nf = [] 1708 | for vi in f: 1709 | nv = rawdata[0][vi] 1710 | nf.append(func_data["verts"].index([nv.x, nv.y, nv.z])) 1711 | func_data["faces"].append(nf) 1712 | func_data["matindex"].append(len(rawdata[1])) 1713 | else: 1714 | f = [] 1715 | ov = face.OuterWire.OrderedVertexes 1716 | for v in ov: 1717 | vl = [v.X, v.Y, v.Z] 1718 | if vl not in func_data["verts"]: 1719 | func_data["verts"].append(vl) 1720 | f.append(func_data["verts"].index(vl)) 1721 | # FreeCAD doesn't care about func_data["verts"] order. 1722 | # Make sure our loop goes clockwise 1723 | c = face.CenterOfMass 1724 | v1 = ov[0].Point.sub(c) 1725 | v2 = ov[1].Point.sub(c) 1726 | n = face.normalAt(0, 0) 1727 | if (v1.cross(v2)).getAngle(n) > 1.57: 1728 | # inverting func_data["verts"] order 1729 | # if the direction is counterclockwise 1730 | f.reverse() 1731 | func_data["faces"].append(f) 1732 | func_data["matindex"].append(1) 1733 | for e in face.Edges: 1734 | faceedges.append(e.hashCode()) 1735 | 1736 | def handle_shape_faces(self, func_data, shape, faceedges): 1737 | """Convert faces to polygons.""" 1738 | if TRIANGULATE: 1739 | # triangulate and make faces 1740 | rawdata = shape.tessellate(self.config["tessellation"]) 1741 | for v in rawdata[0]: 1742 | func_data["verts"].append([v.x, v.y, v.z]) 1743 | for f in rawdata[1]: 1744 | func_data["faces"].append(f) 1745 | for face in shape.Faces: 1746 | for e in face.Edges: 1747 | faceedges.append(e.hashCode()) 1748 | else: 1749 | # write FreeCAD faces as polygons when possible 1750 | for face in shape.Faces: 1751 | self.convert_face_to_polygon(func_data, face, faceedges) 1752 | 1753 | def create_mesh_from_shape(self, func_data): 1754 | """Create mesh from shape.""" 1755 | # print(func_data["pre_line"] + "create_mesh_from_shape") 1756 | # a placeholder to store edges that belong to a face 1757 | faceedges = [] 1758 | shape = func_data["obj"].Shape 1759 | # func_data["freecad_mesh_hash"] = shape.hashCode() 1760 | # hashCode changes on every file opening :-( 1761 | if self.config["placement"]: 1762 | shape = func_data["obj"].Shape.copy() 1763 | shape.Placement = ( 1764 | func_data["obj"].Placement.inverse().multiply(shape.Placement) 1765 | ) 1766 | if shape.Faces: 1767 | self.handle_shape_faces(func_data, shape, faceedges) 1768 | # Treat remaining edges (that are not in faces) 1769 | for edge in shape.Edges: 1770 | if not (edge.hashCode() in faceedges): 1771 | self.handle_shape_edge(func_data, edge) 1772 | return shape 1773 | 1774 | def handle__PartFeature(self, func_data): 1775 | """Handle Part::Feature objects.""" 1776 | pre_line_orig = func_data["pre_line"] 1777 | pre_line = func_data["pre_line"] 1778 | print(func_data["pre_line"] + "handle__PartFeature") 1779 | pre_line += "> " 1780 | func_data["pre_line"] = pre_line 1781 | 1782 | obj = func_data["obj"] 1783 | obj_label = self.get_obj_label(obj) 1784 | if func_data["is_link"] and func_data["obj_label"]: 1785 | obj_label = func_data["obj_label"] 1786 | 1787 | # import_it = False 1788 | update_placement = False 1789 | # check if this Part::Feature object is already imported. 1790 | if self.config["links_as_collectioninstance"]: 1791 | if ( 1792 | obj_label in self.link_targets.children 1793 | and obj_label in bpy.data.objects 1794 | ): 1795 | # print( 1796 | # pre_line + "found link target object '{}'" 1797 | # "".format(obj_label) 1798 | # ) 1799 | bobj_link_target = bpy.data.objects[obj_label] 1800 | # bobj_link_target_label = self.fix_link_target_name( 1801 | self.fix_link_target_name(bobj_link_target) 1802 | # print( 1803 | # pre_line + "fixed name. '{}'" 1804 | # "".format(bobj_link_target) 1805 | # ) 1806 | # self.add_or_update_link_target( 1807 | # func_data=func_data, 1808 | # obj=obj, 1809 | # obj_linked=obj_linkedobj, 1810 | # obj_linkedobj_label=bobj_link_target_label, 1811 | # ) 1812 | self.add_or_update_collection_instance( 1813 | func_data=func_data, 1814 | obj=obj, 1815 | obj_label=obj_label, 1816 | # instance_target_label=bobj_link_target_label, 1817 | instance_target_label=obj_label, 1818 | ) 1819 | else: 1820 | # import_it = True 1821 | pass 1822 | else: 1823 | # handle creation of linked copies 1824 | print(pre_line + "handle creation of linked copies..") 1825 | # print(pre_line + "imported_obj_names:", self.imported_obj_names) 1826 | if ( 1827 | obj_label 1828 | in bpy.data.objects 1829 | # and obj_label in self.imported_obj_names 1830 | ): 1831 | print(pre_line + "→ update bobj") 1832 | bobj = bpy.data.objects[obj_label] 1833 | func_data["bobj"] = bobj 1834 | if not func_data["is_link"]: 1835 | update_placement = True 1836 | func_data["update_tree"] = True 1837 | else: 1838 | print(pre_line + "→ just import it") 1839 | # import_it = True 1840 | 1841 | # if import_it: 1842 | self.create_mesh_from_shape(func_data) 1843 | if func_data["verts"] and (func_data["faces"] or func_data["edges"]): 1844 | self.add_or_update_blender_obj(func_data) 1845 | func_data["update_tree"] = True 1846 | 1847 | if update_placement: 1848 | # print(pre_line + "update_placement..") 1849 | self.handle_placement( 1850 | pre_line, obj, func_data["bobj"], 1851 | ) 1852 | 1853 | # restore 1854 | func_data["pre_line"] = pre_line_orig 1855 | 1856 | # Mesh::Feature 1857 | def handle__MeshFeature(self, func_data): 1858 | """Convert freecad mesh to blender mesh.""" 1859 | mesh = func_data["obj"].Mesh 1860 | if self.config["placement"]: 1861 | # in meshes, this zeroes the placement 1862 | mesh = func_data["obj"].Mesh.copy() 1863 | t = mesh.Topology 1864 | func_data["verts"] = [[v.x, v.y, v.z] for v in t[0]] 1865 | func_data["faces"] = t[1] 1866 | 1867 | # ########################################## 1868 | # main object import 1869 | def create_func_data(self): 1870 | "Create a blank func_data structure." 1871 | func_data = { 1872 | "obj": None, 1873 | "bobj": None, 1874 | "obj_label": None, 1875 | "verts": [], 1876 | "edges": [], 1877 | "faces": [], 1878 | "freecad_mesh_hash": None, 1879 | # face to material relationship 1880 | "matindex": [], 1881 | # to store reusable materials 1882 | "matdatabase": {}, 1883 | # name: "Unnamed", 1884 | "link_targets": [], 1885 | "collection": None, 1886 | "collection_parent": None, 1887 | "parent_obj": None, 1888 | "parent_bobj": None, 1889 | "pre_line": "", 1890 | "update_tree": False, 1891 | "is_link": False, 1892 | "link_source": None, 1893 | } 1894 | return func_data 1895 | 1896 | def _import_obj__handle_type(self, func_data, pre_line=""): 1897 | """Choose Import Type.""" 1898 | obj = func_data["obj"] 1899 | if obj.isDerivedFrom("Part::FeaturePython"): 1900 | self.handle__PartFeaturePython(func_data, pre_line) 1901 | elif obj.isDerivedFrom("Part::Feature"): 1902 | self.handle__PartFeature(func_data) 1903 | elif obj.isDerivedFrom("Mesh::Feature"): 1904 | self.handle__MeshFeature(func_data) 1905 | # elif obj.isDerivedFrom("PartDesign::Body"): 1906 | # self.create_mesh_from_Body(func_data) 1907 | # elif obj.isDerivedFrom("XXXXXX"): 1908 | # self.handle__XXXXXX(func_data) 1909 | elif obj.isDerivedFrom("App::Part"): 1910 | self.handle__AppPart(func_data) 1911 | elif obj.isDerivedFrom("App::LinkElement"): 1912 | # self.handle__AppLinkElement(func_data) 1913 | self.handle__AppLink(func_data) 1914 | elif obj.isDerivedFrom("App::Link"): 1915 | self.handle__AppLink(func_data) 1916 | else: 1917 | self.config["report"]( 1918 | {"WARNING"}, 1919 | ( 1920 | "Unable to load '{}' ('{}') of type '{}'. " 1921 | "(Type Not implemented yet)." 1922 | "".format(obj.Label, obj.Name, obj.TypeId) 1923 | ), 1924 | pre_line, 1925 | ) 1926 | return func_data 1927 | 1928 | def import_obj( 1929 | self, 1930 | func_data=None, 1931 | # obj=None, 1932 | # collection=None, 1933 | # collection_parent=None, 1934 | # parent_obj=None, 1935 | # parent_bobj=None, 1936 | pre_line="", 1937 | ): 1938 | """Import Object.""" 1939 | # import some FreeCAD modules needed below. 1940 | # After "import FreeCAD" these modules become available 1941 | # import Part 1942 | # import PartDesign 1943 | # print("import_obj: obj", obj) 1944 | # dict for storing all data 1945 | if not func_data: 1946 | func_data = self.create_func_data() 1947 | func_data["pre_line"] = pre_line 1948 | obj = func_data["obj"] 1949 | if obj: 1950 | self._import_obj__handle_type(func_data, pre_line) 1951 | 1952 | if func_data["update_tree"]: 1953 | self.update_tree_collections(func_data) 1954 | self.update_tree_parents(func_data) 1955 | return func_data 1956 | 1957 | def import_doc_content(self, doc): 1958 | """Import document content = filterd objects.""" 1959 | pre_line = "" 1960 | obj_list, obj_list_withHost = fc_helper.get_root_objects( 1961 | doc, filter_list=self.typeid_filter_list 1962 | ) 1963 | print("-" * 21) 1964 | 1965 | self.config["report"]( 1966 | {"INFO"}, 1967 | ( 1968 | "found {} root objects in '{}'" 1969 | "".format(len(obj_list), self.doc_filename) 1970 | ), 1971 | pre_line=pre_line, 1972 | ) 1973 | fc_helper.print_objects(obj_list, show_lists=True, show_list_details=True) 1974 | print("-" * 21) 1975 | 1976 | self.config["report"]( 1977 | {"INFO"}, 1978 | ( 1979 | "found {} objects with Hosts attribute set in '{}' - will be handled as childs.." 1980 | "".format(len(obj_list_withHost), self.doc_filename) 1981 | ), 1982 | pre_line=pre_line, 1983 | ) 1984 | fc_helper.print_objects( 1985 | obj_list_withHost, show_lists=True, show_list_details=True 1986 | ) 1987 | print("-" * 21) 1988 | # self.config["report"]( 1989 | # {"INFO"}, 1990 | # ("the Hosts ARCH way is not implemented yet. so we just import them."), 1991 | # pre_line=pre_line, 1992 | # ) 1993 | # obj_list.extend(obj_list_withHost) 1994 | # fc_helper.print_objects(obj_list, show_lists=True) 1995 | # print("-" * 21) 1996 | 1997 | # │─ ┌─ └─ ├─ ╞═ ╘═╒═ 1998 | # ║═ ╔═ ╚═ ╠═ ╟─ 1999 | # ┃━ ┏━ ┗━ ┣━ ┠─ 2000 | pre_line_start = pre_line + "┏━━━━ " 2001 | pre_line_sub = pre_line + "┣━ " 2002 | pre_line_follow = pre_line + "┃ " 2003 | pre_line_end = pre_line + "┗━━━━ " 2004 | self.config["report"]({"INFO"}, "Import", pre_line=pre_line_start) 2005 | for obj in obj_list: 2006 | if self.check_obj_visibility_with_skiphidden(obj): 2007 | self.print_obj(obj, pre_line=pre_line_sub) 2008 | func_data_new = self.create_func_data() 2009 | func_data_new["obj"] = obj 2010 | func_data_new["collection"] = self.fcstd_collection 2011 | func_data_new["parent_bobj"] = self.fcstd_empty 2012 | self.import_obj( 2013 | func_data=func_data_new, pre_line=pre_line_follow, 2014 | ) 2015 | if obj in obj_list_withHost: 2016 | self.config["report"]( 2017 | {"INFO"}, 2018 | ("TODO: handle Hosts [{}] of obj '{}'").format(obj.Hosts, obj), 2019 | pre_line=pre_line_follow, 2020 | ) 2021 | else: 2022 | self.print_obj( 2023 | obj=obj, 2024 | pre_line=pre_line_sub, 2025 | post_line=( 2026 | b_helper.colors.fg.darkgrey 2027 | + " (skipping - hidden)" 2028 | + b_helper.colors.reset 2029 | ), 2030 | ) 2031 | self.config["report"]({"INFO"}, "finished.", pre_line=pre_line_end) 2032 | 2033 | def prepare_collection(self): 2034 | """Prepare main import collection.""" 2035 | link_targets_label = self.doc.Name + "__link_targets" 2036 | if self.config["update"]: 2037 | if self.doc_filename in bpy.data.collections: 2038 | self.fcstd_collection = bpy.data.collections[self.doc_filename] 2039 | if link_targets_label in bpy.data.collections: 2040 | self.link_targets = bpy.data.collections[link_targets_label] 2041 | 2042 | if not self.fcstd_collection: 2043 | self.fcstd_collection = bpy.data.collections.new(self.doc_filename) 2044 | bpy.context.scene.collection.children.link(self.fcstd_collection) 2045 | 2046 | if not self.link_targets: 2047 | self.link_targets = bpy.data.collections.new(link_targets_label) 2048 | self.fcstd_collection.children.link(self.link_targets) 2049 | # hide this internal object. 2050 | # we use only the instances.. 2051 | self.link_targets.hide_render = False 2052 | self.link_targets.hide_select = True 2053 | self.link_targets.hide_viewport = False 2054 | # exclude from all view layers 2055 | for lc in helper.find_layer_collection_in_scene( 2056 | collection_name=link_targets_label 2057 | ): 2058 | lc.exclude = True 2059 | 2060 | def prepare_root_empty(self): 2061 | """Prepare import file root empty.""" 2062 | func_data = { 2063 | "obj": None, 2064 | "parent_obj": None, 2065 | "parent_bobj": None, 2066 | "collection": self.fcstd_collection, 2067 | "pre_line": "", 2068 | } 2069 | self.fcstd_empty = self.parent_empty_add_or_update(func_data, self.doc_filename) 2070 | 2071 | def append_path(self, path, sub=""): 2072 | if path and sub: 2073 | path = os.path.join(path, sub) 2074 | print("full path:", path) 2075 | if path and os.path.exists(path): 2076 | if os.path.isfile(path): 2077 | path = os.path.dirname(path) 2078 | print("configured path:", path) 2079 | if path not in sys.path: 2080 | sys.path.append(path) 2081 | else: 2082 | self.config["report"]( 2083 | {"WARNING"}, ("Path does not exist. Please check! " "'{}'".format(path)) 2084 | ) 2085 | 2086 | def prepare_freecad_path(self): 2087 | """Find FreeCAD libraries.""" 2088 | # https://github.com/s-light/io_import_fcstd/issues/11 2089 | 2090 | # check user specified location specified in addon preferences 2091 | 2092 | # try snap 2093 | # "/snap/freecad/current/usr/lib/" 2094 | 2095 | # try appimage 2096 | self.appimage_mounted = False 2097 | # my.AppImage --appimage-mount 2098 | # use mountingpoitn. 2099 | 2100 | # try flatpack 2101 | 2102 | # set path to new location 2103 | # self.path_to_freecad 2104 | # self.path_to_system_packages 2105 | 2106 | def prepare_freecad_import(self): 2107 | """Prepare FreeCAD import.""" 2108 | self.append_path(self.path_to_freecad) 2109 | self.append_path(self.path_to_system_packages) 2110 | 2111 | def cleanup_freecad_import(self): 2112 | """Cleanup if nessesary.""" 2113 | if self.appimage_mounted: 2114 | pass 2115 | 2116 | 2117 | def handle_additonal_paths(self): 2118 | """Prepare more paths for import.""" 2119 | import FreeCAD 2120 | 2121 | path_base = FreeCAD.getResourceDir() # noqa 2122 | # https://wiki.freecadweb.org/PySide 2123 | # /usr/share/freecad-daily/Ext/PySide 2124 | self.append_path(path_base, "Ext") 2125 | self.append_path(path_base, "Mod") 2126 | 2127 | def import_extras(self): 2128 | """Import additional things.""" 2129 | self.handle_additonal_paths() 2130 | try: 2131 | import Part # noqa 2132 | 2133 | # import PartDesign # noqa 2134 | import Draft # noqa 2135 | import Arch # noqa 2136 | except ModuleNotFoundError as e: 2137 | self.config["report"]( 2138 | {"ERROR"}, 2139 | "Unable to import one of the additional modules. \n" 2140 | "\n" 2141 | "Make sure it can be found by Python, \n" 2142 | "you might need to set its path in this Addon preferences.. " 2143 | "(User preferences->Addons->expand this addon).\n" 2144 | "\n" + str(e), 2145 | ) 2146 | return {"CANCELLED"} 2147 | except Exception as e: 2148 | self.config["report"]({"ERROR"}, "Import Failed.\n" "\n" + str(e)) 2149 | return {"CANCELLED"} 2150 | 2151 | def import_fcstd(self, filename=None): 2152 | """Read a FreeCAD .FCStd file and creates Blender objects.""" 2153 | if filename: 2154 | self.config["filename"] = filename 2155 | 2156 | try: 2157 | self.prepare_freecad_path() 2158 | self.prepare_freecad_import() 2159 | import FreeCAD 2160 | except ModuleNotFoundError as e: 2161 | self.config["report"]( 2162 | {"ERROR"}, 2163 | "Unable to import the FreeCAD Python module. \n" 2164 | "\n" 2165 | "Make sure FreeCAD is installed on your system! \n" 2166 | "and compiled with Python3 (same version as Blender).\n" 2167 | "We tried to search for it - \n" 2168 | "but maybee its easier to set its path in this Addon preferences " 2169 | "(User preferences->Addons->expand this addon).\n" 2170 | "\n" + str(e), 2171 | ) 2172 | return {"CANCELLED"} 2173 | except Exception as e: 2174 | self.config["report"]({"ERROR"}, "Import Failed.\n" "\n" + str(e)) 2175 | return {"CANCELLED"} 2176 | finally: 2177 | self.cleanup_freecad_import() 2178 | 2179 | self.import_extras() 2180 | 2181 | self.guidata = guidata.load_guidata( 2182 | self.config["filename"], self.config["report"], 2183 | ) 2184 | 2185 | # Context Managers not implemented.. 2186 | # see https://docs.python.org/3.8/reference/compound_stmts.html#with 2187 | # with FreeCAD.open(self.config["filename"]) as doc: 2188 | # so we use the classic try finally block: 2189 | try: 2190 | # doc = FreeCAD.open( 2191 | # "/home/stefan/mydata/freecad/tests/linking_test/Linking.FCStd") 2192 | self.config["report"]( 2193 | {"INFO"}, "open FreeCAD file. '{}'" "".format(self.config["filename"]) 2194 | ) 2195 | try: 2196 | doc = FreeCAD.open(self.config["filename"]) 2197 | except Exception as e: 2198 | print(e) 2199 | docname = doc.Name 2200 | if doc: 2201 | self.doc_filename = doc.Name + ".FCStd" 2202 | self.config["report"]( 2203 | {"INFO"}, 2204 | "File '{}' successfully opened." "".format(self.doc_filename), 2205 | ) 2206 | self.doc = doc 2207 | # self.print_debug_report() 2208 | self.config["report"]({"INFO"}, "recompute..") 2209 | self.doc.recompute() 2210 | # self.config["report"]({'INFO'}, "importLinks..") 2211 | # self.doc.importLinks() 2212 | # importLinks is currently not reliable.. 2213 | # self.config["report"]({'INFO'}, "recompute..") 2214 | # self.doc.recompute() 2215 | self.prepare_collection() 2216 | self.prepare_root_empty() 2217 | self.import_doc_content(doc) 2218 | else: 2219 | self.config["report"]( 2220 | {"ERROR"}, 2221 | "Unable to open the given FreeCAD file '{}'" 2222 | "".format(self.config["filename"]), 2223 | ) 2224 | return {"CANCELLED"} 2225 | except Exception as e: 2226 | self.config["report"]({"ERROR"}, str(e)) 2227 | raise e 2228 | finally: 2229 | FreeCAD.closeDocument(docname) 2230 | print("Import finished.") 2231 | return {"FINISHED"} 2232 | 2233 | 2234 | def main_test(): 2235 | """Tests.""" 2236 | pass 2237 | 2238 | 2239 | if __name__ == "__main__": 2240 | main_test() 2241 | -------------------------------------------------------------------------------- /import_fcstd/guidata.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | """XML handler.""" 5 | 6 | import xml.sax 7 | import zipfile 8 | 9 | 10 | class FreeCAD_xml_handler(xml.sax.ContentHandler): 11 | """ 12 | A XML handler to process the FreeCAD GUI xml data. 13 | 14 | this creates a dictionary where each key is a FC object name, 15 | and each value is a dictionary of property:value pairs 16 | """ 17 | 18 | def __init__(self): 19 | """Init.""" 20 | self.guidata = {} 21 | self.current = None 22 | self.properties = {} 23 | self.currentprop = None 24 | self.currentval = None 25 | 26 | def startElement(self, tag, attributes): 27 | """Call when an element starts.""" 28 | if tag == "ViewProvider": 29 | self.current = attributes["name"] 30 | elif tag == "Property": 31 | name = attributes["name"] 32 | element_names = [ 33 | "Visibility", 34 | "ShapeColor", 35 | "Transparency", 36 | "DiffuseColor" 37 | ] 38 | if name in element_names: 39 | self.currentprop = name 40 | elif tag == "Bool": 41 | if attributes["value"] == "true": 42 | self.currentval = True 43 | else: 44 | self.currentval = False 45 | elif tag == "PropertyColor": 46 | c = int(attributes["value"]) 47 | r = float((c >> 24) & 0xFF)/255.0 48 | g = float((c >> 16) & 0xFF)/255.0 49 | b = float((c >> 8) & 0xFF)/255.0 50 | self.currentval = (r, g, b) 51 | elif tag == "Integer": 52 | self.currentval = int(attributes["value"]) 53 | elif tag == "Float": 54 | self.currentval = float(attributes["value"]) 55 | elif tag == "ColorList": 56 | self.currentval = attributes["file"] 57 | 58 | def endElement(self, tag): 59 | """Call when an elements ends.""" 60 | if tag == "ViewProvider": 61 | if self.current and self.properties: 62 | self.guidata[self.current] = self.properties 63 | self.current = None 64 | self.properties = {} 65 | elif tag == "Property": 66 | if self.currentprop and (self.currentval is not None): 67 | self.properties[self.currentprop] = self.currentval 68 | self.currentprop = None 69 | self.currentval = None 70 | 71 | 72 | def load_guidata(filename, report): 73 | """Check if we have a GUI document.""" 74 | report({'INFO'}, "load guidata..") 75 | guidata = None 76 | zdoc = zipfile.ZipFile(filename) 77 | if zdoc: 78 | if "GuiDocument.xml" in zdoc.namelist(): 79 | gf = zdoc.open("GuiDocument.xml") 80 | guidata = gf.read() 81 | gf.close() 82 | Handler = FreeCAD_xml_handler() 83 | xml.sax.parseString(guidata, Handler) 84 | guidata = Handler.guidata 85 | for key, properties in guidata.items(): 86 | # open each diffusecolor files and retrieve values 87 | # first 4 bytes are the array length, 88 | # then each group of 4 bytes is abgr 89 | if "DiffuseColor" in properties: 90 | # print ("opening:",guidata[key]["DiffuseColor"]) 91 | df = zdoc.open(guidata[key]["DiffuseColor"]) 92 | buf = df.read() 93 | # print (buf," length ",len(buf)) 94 | df.close() 95 | cols = [] 96 | for i in range(1, int(len(buf)/4)): 97 | cols.append( 98 | (buf[i*4+3], buf[i*4+2], buf[i*4+1], buf[i*4])) 99 | guidata[key]["DiffuseColor"] = cols 100 | zdoc.close() 101 | report({'INFO'}, "load guidata done.") 102 | # print("guidata:", guidata) 103 | return guidata 104 | -------------------------------------------------------------------------------- /import_fcstd/helper.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | """Helper.""" 5 | 6 | import bpy 7 | 8 | 9 | def rename_old_data(data, data_label): 10 | """Recusive add '_old' to data object.""" 11 | name_old = None 12 | if data_label in data: 13 | name_old = data[data_label].name + "_old" 14 | if name_old in data: 15 | # rename recusive.. 16 | rename_old_data(data, name_old) 17 | data[data_label].name = name_old 18 | return name_old 19 | 20 | 21 | def find_layer_collection_recusive(*, collection_name, layer_collection): 22 | """Recursivly transverse layer_collection for a particular name.""" 23 | result_layer_collection = None 24 | if layer_collection.name == collection_name: 25 | result_layer_collection = layer_collection 26 | else: 27 | for lc in layer_collection.children: 28 | temp = find_layer_collection_recusive( 29 | collection_name=collection_name, 30 | layer_collection=lc 31 | ) 32 | if temp: 33 | result_layer_collection = temp 34 | return result_layer_collection 35 | 36 | 37 | # def find_layer_collection_in_view_layer(collection_name, view_layer): 38 | # """Find layer_collection in view_layer.""" 39 | # result_layer_collection = find_layer_collection_recusive( 40 | # collection_name=collection_name, 41 | # layer_collection=view_layer.layer_collection 42 | # ) 43 | # return result_layer_collection 44 | 45 | 46 | def find_layer_collection_in_scene(*, collection_name, scene=None): 47 | """Find layer_collection in scene.""" 48 | result_layer_collections = [] 49 | if scene is None: 50 | # use active scene 51 | scene = bpy.context.scene 52 | for view_layer in scene.view_layers: 53 | result_layer_collections.append( 54 | find_layer_collection_recusive( 55 | collection_name=collection_name, 56 | layer_collection=view_layer.layer_collection 57 | ) 58 | ) 59 | return result_layer_collections 60 | -------------------------------------------------------------------------------- /import_fcstd/material.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | """Material Things.""" 5 | 6 | import bpy 7 | from bpy_extras.node_shader_utils import PrincipledBSDFWrapper 8 | 9 | from .. import blender_helper as b_helper 10 | 11 | # from . import helper 12 | 13 | 14 | class MaterialManager(object): 15 | """ 16 | Handle all Material related things. 17 | """ 18 | 19 | def __init__( 20 | self, 21 | guidata, 22 | func_data, 23 | bobj, 24 | obj_label, 25 | sharemats, 26 | report=None, 27 | report_preline="", 28 | ): 29 | """Init.""" 30 | self.report_fnc = report 31 | self.report_preline = report_preline 32 | self.guidata = guidata 33 | self.func_data = func_data 34 | self.bobj = bobj 35 | self.obj_label = obj_label 36 | self.sharemats = sharemats 37 | 38 | def report(self, data, mode=None, pre_line=None): 39 | if not mode: 40 | mode = {"INFO"} 41 | if not pre_line: 42 | pre_line = self.report_preline 43 | else: 44 | pre_line = self.report_preline + pre_line 45 | if self.report_fnc: 46 | return self.report_fnc(mode, data, pre_line=pre_line) 47 | else: 48 | print(pre_line + data) 49 | 50 | # material 51 | def get_obj_Transparency(self, obj_Name): 52 | """Get object Transparency and convert to blender units.""" 53 | alpha = 1.0 54 | if "Transparency" in self.guidata[obj_Name]: 55 | if self.guidata[obj_Name]["Transparency"] > 0: 56 | alpha = (100 - self.guidata[obj_Name]["Transparency"]) / 100.0 57 | return alpha 58 | 59 | def get_obj_ShapeColor(self, obj_Name): 60 | """Get object ShapeColor and convert to blender units.""" 61 | rgb = (0.5, 0.5, 0.5) 62 | if "ShapeColor" in self.guidata[obj_Name]: 63 | rgb = self.guidata[obj_Name]["ShapeColor"] 64 | return rgb 65 | 66 | def get_obj_DiffuseColor(self, obj_Name, i): 67 | """Get object DiffuseColor and convert to blender units.""" 68 | # DiffuseColor stores int values, Blender use floats 69 | rgba = tuple( 70 | [float(x) / 255.0 for x in self.guidata[obj_Name]["DiffuseColor"][i]] 71 | ) 72 | return rgba 73 | 74 | def get_obj_rgba(self, obj_Name, mat_index=None): 75 | """Get object rgba value in blender usable format.""" 76 | if mat_index: 77 | rgba = self.get_obj_DiffuseColor(obj_Name, mat_index) 78 | # FreeCAD stores transparency, not alpha 79 | alpha = 1.0 80 | if rgba[3] > 0: 81 | alpha = 1.0 - rgba[3] 82 | rgba = rgba[:3] + (alpha,) 83 | else: 84 | alpha = self.get_obj_Transparency(obj_Name) 85 | rgb = self.get_obj_ShapeColor(obj_Name) 86 | rgba = rgb + (alpha,) 87 | return rgba 88 | 89 | def create_new_bmat(self, bmat_name, rgba): 90 | """Create new blender material.""" 91 | bmat = bpy.data.materials.new(name=bmat_name) 92 | bmat.use_nodes = True 93 | # link bmat to PrincipledBSDFWrapper 94 | principled = PrincipledBSDFWrapper(bmat, is_readonly=False) 95 | principled.base_color = rgba[:3] 96 | # check for alpha 97 | if rgba[3] < 1.0: 98 | bmat.diffuse_color = rgba 99 | principled.alpha = rgba[3] 100 | bmat.blend_method = "BLEND" 101 | if self.sharemats: 102 | self.func_data["matdatabase"][rgba] = bmat 103 | return bmat 104 | 105 | def handle_material_per_face(self, face_index, objmats, material_index): 106 | """Handle material for face.""" 107 | # Create new mats and attribute faces to them 108 | # DiffuseColor stores int values, Blender use floats 109 | # self.report( 110 | # b_helper.colors.fg.lightblue 111 | # + "handle_material_per_face" 112 | # + b_helper.colors.reset, 113 | # pre_line="| ", 114 | # ) 115 | rgba = self.get_obj_rgba(self.func_data["obj"].Name, material_index) 116 | # get or create blender material 117 | bmat = None 118 | if self.sharemats: 119 | if rgba in self.func_data["matdatabase"]: 120 | bmat = self.func_data["matdatabase"][rgba] 121 | if rgba not in objmats: 122 | objmats.append(rgba) 123 | self.bobj.data.materials.append(bmat) 124 | if not bmat: 125 | if rgba in objmats: 126 | bmat = self.bobj.data.materials[objmats.index(rgba)] 127 | if not bmat: 128 | bmat_name = self.obj_label + "_" + str(len(objmats)) 129 | bmat = self.create_new_bmat(bmat_name, rgba) 130 | objmats.append(rgba) 131 | self.bobj.data.materials.append(bmat) 132 | 133 | # at this point we should have a valid blender material 134 | 135 | # self.report( 136 | # b_helper.colors.fg.lightblue 137 | # + "objmats " 138 | # + b_helper.colors.reset 139 | # + "{}".format(objmats), 140 | # pre_line="| ", 141 | # ) 142 | # self.report( 143 | # b_helper.colors.fg.lightblue 144 | # + "bmat " 145 | # + b_helper.colors.reset 146 | # + "{}".format(bmat), 147 | # pre_line="| ", 148 | # ) 149 | # self.report( 150 | # b_helper.colors.fg.lightblue 151 | # + "face_index " 152 | # + b_helper.colors.reset 153 | # + "{}".format(face_index), 154 | # pre_line="| ", 155 | # ) 156 | 157 | # assigne materials to polygons 158 | objmats_index = objmats.index(rgba) 159 | # self.report( 160 | # b_helper.colors.fg.lightblue 161 | # + "objmats_index " 162 | # + b_helper.colors.reset 163 | # + "{}".format(objmats_index), 164 | # pre_line="| ", 165 | # ) 166 | # self.report( 167 | # b_helper.colors.fg.lightblue 168 | # + 'self.func_data["matindex"][material_index] ' 169 | # + b_helper.colors.reset 170 | # + "{}".format(self.func_data["matindex"][material_index]), 171 | # pre_line="| ", 172 | # ) 173 | 174 | for fj in range(self.func_data["matindex"][material_index]): 175 | # self.report( 176 | # b_helper.colors.fg.lightblue 177 | # + "fj " 178 | # + b_helper.colors.reset 179 | # + "{}".format(fj), 180 | # pre_line="| * ", 181 | # ) 182 | # self.report( 183 | # b_helper.colors.fg.lightblue 184 | # + "face_index + fj " 185 | # + b_helper.colors.reset 186 | # + "{}".format(face_index + fj), 187 | # pre_line="| * ", 188 | # ) 189 | self.bobj.data.polygons[face_index + fj].material_index = objmats_index 190 | face_index += self.func_data["matindex"][material_index] 191 | return face_index 192 | 193 | def handle_material_multi(self): 194 | """Handle multi material.""" 195 | # we have per-face materials. 196 | # self.report( 197 | # b_helper.colors.fg.lightgreen 198 | # + "handle_material_multi" 199 | # + b_helper.colors.reset, 200 | # pre_line="| ", 201 | # ) 202 | face_index = 0 203 | objmats = [] 204 | for material_index in range(len(self.func_data["matindex"])): 205 | face_index = self.handle_material_per_face( 206 | face_index, objmats, material_index 207 | ) 208 | 209 | def handle_material_single(self): 210 | """Handle single material.""" 211 | # one material for the whole object 212 | self.report( 213 | b_helper.colors.fg.lightgreen 214 | + "handle_material_single" 215 | + b_helper.colors.reset 216 | ) 217 | rgba = self.get_obj_rgba(self.func_data["obj"].Name) 218 | bmat = None 219 | if self.sharemats: 220 | if rgba in self.func_data["matdatabase"]: 221 | bmat = self.func_data["matdatabase"][rgba] 222 | else: 223 | # print("not found in db:",rgba,"in",matdatabase) 224 | pass 225 | if not bmat: 226 | bmat_name = self.obj_label 227 | bmat = self.create_new_bmat(bmat_name, rgba) 228 | self.bobj.data.materials.append(bmat) 229 | 230 | def create_new(self): 231 | """Handle material creation.""" 232 | # check if we have a material at all... 233 | # self.report( 234 | # b_helper.colors.fg.lightgreen 235 | # + "create_new material" 236 | # + b_helper.colors.reset 237 | # ) 238 | if self.func_data["obj"].Name in self.guidata: 239 | # check if we have 'per face' or 'object' coloring. 240 | # self.report( 241 | # b_helper.colors.bold 242 | # + b_helper.colors.fg.lightblue 243 | # + 'self.func_data["matindex"]' 244 | # + " ({}): ".format(len(self.func_data["matindex"])) 245 | # + b_helper.colors.reset 246 | # + "{}".format(self.func_data["matindex"]) 247 | # ) 248 | # # ############ 249 | # # list colors: 250 | # self.report( 251 | # b_helper.colors.bold 252 | # + b_helper.colors.fg.lightblue 253 | # + 'self.guidata[self.func_data["obj"].Name]["DiffuseColor"]' 254 | # + " ({}):".format( 255 | # len(self.guidata[self.func_data["obj"].Name]["DiffuseColor"]) 256 | # ) 257 | # + b_helper.colors.reset 258 | # ) 259 | # for index, color in enumerate( 260 | # self.guidata[self.func_data["obj"].Name]["DiffuseColor"] 261 | # ): 262 | # self.report(" {:>3} {}".format(index, color)) 263 | # # ############ 264 | # 265 | # self.report( 266 | # b_helper.colors.fg.lightblue 267 | # + "self.bobj.data.polygons " 268 | # + b_helper.colors.reset 269 | # + "{}".format(self.bobj.data.polygons) 270 | # ) 271 | 272 | # # create a list with all faces 273 | # face_list = [face for face in self.bobj.data.polygons] 274 | # self.report( 275 | # b_helper.colors.fg.lightblue + "face_list " + b_helper.colors.reset 276 | # ) 277 | # for index, face in enumerate(face_list): 278 | # self.report(" {:>3} {}".format(index, face)) 279 | # # ############ 280 | 281 | # check for multi material 282 | if ( 283 | self.func_data["matindex"] 284 | and ("DiffuseColor" in self.guidata[self.func_data["obj"].Name]) 285 | and ( 286 | len(self.func_data["matindex"]) 287 | == len(self.guidata[self.func_data["obj"].Name]["DiffuseColor"]) 288 | ) 289 | ): 290 | self.handle_material_multi() 291 | else: 292 | self.handle_material_single() 293 | -------------------------------------------------------------------------------- /pylama.ini: -------------------------------------------------------------------------------- 1 | [pylama:pycodestyle] 2 | max_line_length = 100 3 | 4 | [pylama:pylint] 5 | max_line_length = 100 6 | 7 | [pylama:pep8] 8 | max_line_length = 100 9 | --------------------------------------------------------------------------------