Compare commits

...

775 Commits

Author SHA1 Message Date
Dion Moult 7e151799b0 Fix #2650. IFC selector only operates on visible objects. 2023-01-11 15:20:58 +11:00
Dion Moult 597b2bb0a8 Fix #2596. Bug with IFC4X3 UI errors as docs couldn't be fetched. 2023-01-10 12:14:10 +11:00
Dion Moult ddc0f7e652 Theme improvements to IfcOpenShell docs 2023-01-10 11:43:20 +11:00
Jesusbill 4845d191f4 Minor fixes to docs 2023-01-10 01:06:49 +01:00
Dion Moult da84cc5925 Add documentation for IfcPatch and all recipes 2023-01-10 10:18:13 +11:00
Dion Moult c8ba96cde8 Make syntax highlighting explicit in docs 2023-01-10 10:16:28 +11:00
Dion Moult 66e067d1ce Remove IfcMax from documentation as it's no longer relevant 2023-01-10 06:38:53 +11:00
Dion Moult 9d39760f6f Add IfcClash to docs 2023-01-10 06:38:53 +11:00
Sigma Dimensions 8aa4934d92 BBIM: Move out Search/Selection sub-panels from "IFC Collaboration" to "IFC Selection" 2023-01-09 14:49:59 +01:00
Dion Moult f848340124 Fix #2681. Fix 90 degree rotation on walls. 2023-01-09 21:55:22 +11:00
Dion Moult b699f55d9c Deleting a spatial or aggregated element now also removes its collection 2023-01-09 16:46:51 +11:00
Dion Moult 745709c56a Handle orphaned spatial elements when loading projects 2023-01-09 16:46:23 +11:00
Dion Moult 0c4c8ad9bc Fix #1012. Half complete example documentation for the IFC Project panel.
Incredibly incomplete, but serves as an example of how reference user-based documentation should be structured.
2023-01-09 15:24:50 +11:00
Ryan Schultz 3ce010de77 added IfcCivilElement to entity_to_type_map_4.json 2023-01-09 12:14:11 +11:00
Dion Moult dd600dd20f Fix #2024. Boolean diagram included in IfcOpenShell docs. 2023-01-09 11:50:49 +11:00
Dion Moult 360b9956c0 Fix failing tests from refactor which I really should've done before the release 2023-01-09 11:00:46 +11:00
Andrej730 1888048d4d Moved ShapeBuilder to separate module #2679
Moved ShapeBuilder to separate module - now `mathutils` will be only required if you're actually using ShapeBuilder (related to #2679)
2023-01-08 19:47:18 +05:00
Andrej730 4ab6d648c9 sverchok_modifier - minor refactor 2023-01-08 19:47:18 +05:00
Andrej730 c29048ecfb Furniture library bug fixes 2023-01-08 19:47:18 +05:00
Dion Moult 0294c4e02c Fix #2678. Critical typo bug meant that units were fixed to metric millimeters. 2023-01-08 12:58:53 +11:00
Dion Moult 3cfcd847cf Fix #2677. Docs now generate in a reasonable time and exclude generated IFC modules. 2023-01-08 11:12:54 +11:00
Massimo Fabbro 694b21a0e1 Delete unuseful functions and improve speed (#2672)
* Delete unuseful functions and improve speed

* Delete active object function, cleaning around and improve performance

* Better functions names and prevent user defined qto use
2023-01-07 18:11:46 +01:00
Dion Moult 73d43649a5 Link to latest IfcSverchok release 2023-01-07 17:08:46 +11:00
Dion Moult 55036653df Update IfcSverchok loading based on latest Sverchok master 2023-01-07 13:04:30 +11:00
Dion Moult 66ecb92cc2 Minor UX improvements to library name and Sverchok UI 2023-01-07 12:00:22 +11:00
Andrej730 23c7bc3f56 Added ShapeBuilder class and library for non structural assets (
`ShapeBuilder` class is used to programmatically create 2d and 3d ifc shapes and representations. It's inspired by the way OpenSCAD language is designed - the goal was to create a readable and easy way to create new shapes. The class can be imported from `ifcopenshell.util.representation` module.

Examples of things created with `ShapeBuilder` can be found in `shape_builder_examples.py` or `generate_furniture_library.py` / `Non_structural_assets_library.ifc`.

Also added ifc library of non structual assets (furniture etc) - `Non_structural_assets_library.ifc`. All assets are using most common dimensions for them - it makes it good for prototyping. Related to https://community.osarch.org/discussion/1192
2023-01-06 16:35:55 +05:00
Andrej730 74fee24004 Update ifc geometry on sverchok update 2023-01-06 12:23:54 +05:00
Dion Moult 618ecccb7b Fix #2428. Document minimum system support. 2023-01-06 17:11:51 +11:00
Dion Moult f818fc659a Bundle ifcopenshell for Python 3.11 on PyPI. See #2551. 2023-01-06 16:37:50 +11:00
Dion Moult 4afada48a6 Fix #2440. Fix #2439. Describe how to access advanced mode when dealing with large models. 2023-01-06 14:01:42 +11:00
Dion Moult 59451cb90c Fix #2664. Package with latest IfcOpenShell build. See #2670. 2023-01-06 11:16:29 +11:00
Sigma Dimensions 53bf746227 BBIM: Fixes for:
- Task Time attributes
- Fix typo animation time calculation
2023-01-05 18:43:50 +01:00
Sigma Dimensions cd6f995471 BBIM: Disable "Create animation" buttons when no start dates/end dates are given 2023-01-05 18:36:22 +01:00
Dion Moult 839a00c2a8 Run black on validation, lazy-load _pytest.assertion so that it isn't a hard dependency for non-express rule validation. 2023-01-05 20:54:17 +11:00
Dion Moult 9208781b8f Bump IfcOpenShell version 2023-01-05 18:38:32 +11:00
Dion Moult 25af42f4cb Fix circular import of json_logger when doing import ifcopenshell.validate 2023-01-05 18:37:14 +11:00
Dion Moult d386a884aa You can now purge properties explicitly instead of setting them to None 2023-01-05 17:37:40 +11:00
Dion Moult cb92be2b08 No need to specify SI unit names as they can be derived from the unit type 2023-01-05 17:37:40 +11:00
Martyna Jakubowska 70b7f1424f Resolved Code Review 2023-01-05 17:37:40 +11:00
Martyna Jakubowska 56858540b8 Update README.md 2023-01-05 17:37:40 +11:00
Martyna Jakubowska 0331a21280 Update README.md 2023-01-05 17:37:40 +11:00
Martyna Jakubowska 9f53d724d1 Update README.md 2023-01-05 17:37:40 +11:00
martinaCodes c114f09f9a Added example model and a corresponding description 2023-01-05 17:37:40 +11:00
martinaCodes 1a2183ef71 Bug fixes in create_entity 2023-01-05 17:37:40 +11:00
martinaCodes 0ab0213dea Bug fixes and changed behaviour of create_entity, so it creates an entity pr. each representation 2023-01-05 17:37:40 +11:00
Martyna Jakubowska 421f861e46 Updated README 2023-01-05 17:37:38 +11:00
martinaCodes 6e5f2f3f8d Updated by_query and removed print statements 2023-01-05 17:36:33 +11:00
martinaCodes 05633cd657 Updated following nodes to work with the current workflow: add_pset, by_guid, get_attribute, get_property, read_entity. Added tooltips and other minor fixes 2023-01-05 17:36:33 +11:00
Martyna Jakubowska 41ae350f33 Create README.md 2023-01-05 17:36:33 +11:00
martinaCodes 8a26f30506 Important rework of handling multiple/nested items in bmesh_to_ifc, create_entity and sverchok_to_ifc nodes. Formatting and other minor fixes. 2023-01-05 17:36:33 +11:00
martinaCodes 4ae7643874 cleaned print statements etc 2023-01-05 17:36:33 +11:00
martinaCodes b09068e1f4 write_file added logic that ensures proper model hierarchy 2023-01-05 17:36:33 +11:00
martinaCodes 87dbaa39d8 bmesh_to_ifc now creates separate representations for each distinct bmesh geometry 2023-01-05 17:36:33 +11:00
martinaCodes d193e8a3eb major changes primarily to file creation/saving logic.
Bug fix in create entity.
Other..
this commit is way to big and unstructured.
2023-01-05 17:36:33 +11:00
martinaCodes f9e766138c new pick_ifc_class node + minor fixes. Add_spatial_element is WIP 2023-01-05 17:36:33 +11:00
martinaCodes 36899d362e added blender panel for saving file + minor fix in create_entity 2023-01-05 17:36:33 +11:00
martinaCodes fd789178c1 Add_spatial_element node updated 2023-01-05 17:36:33 +11:00
martinaCodes a0757851c8 Add_spacial_element node updated to work with multiple elements. V0.1 - still a bit buggy. 2023-01-05 17:36:33 +11:00
martinaCodes 039727cb19 added entity locations 2023-01-05 17:36:33 +11:00
martinaCodes b5133f1c41 write_file node updated 2023-01-05 17:36:33 +11:00
martinaCodes ab218b842a create_shape node updated + minor changes 2023-01-05 17:36:33 +11:00
martinaCodes 37aa17fcf3 sverchok_to_ifc node added + works for multiple geometries. Parent contexts also get deleted if not used. 2023-01-05 17:36:33 +11:00
martinaCodes 6542fc7db3 updated by_type to current workflow + added sv_to_ifc 2023-01-05 17:36:33 +11:00
martinaCodes c8c690afc2 changes
1) pasing ID references
2) bugs in create_entities, where number of representations couldn't be updated
3) by_id node updated to current workflow
... other small fixes
2023-01-05 17:36:33 +11:00
martinaCodes 5d569dea30 fixed create_entity and minor changes to bmesh_to_ifc 2023-01-05 17:36:33 +11:00
martinaCodes 38031a02bc new nodes, and changes to create_entity2, but not working yet 2023-01-05 17:36:33 +11:00
martinaCodes 25460661ab created new singleton class SvIfcStore, created new 'create_entity2' node 2023-01-05 17:36:33 +11:00
martinaCodes 3684be1936 fixed api_WIP, minor cleanup in other nodes 2023-01-05 17:36:33 +11:00
martinaCodes 0ada921ad8 fixed a typo-bug 2023-01-05 17:36:33 +11:00
martinaCodes 8fb285596a added new nodes to node index 2023-01-05 17:36:33 +11:00
martinaCodes ba4130d395 Added new nodes: api_WIP, bmesh_to_ifc, create_project and quick_project_setup
Modified nodes: api, create_entity
2023-01-05 17:36:33 +11:00
Sigma Dimensions f818446e7e BBIM fix: prevent "guess_all_quantities" operator from showing up on Property Sets 2023-01-04 13:59:00 +01:00
Dion Moult 82ac895bbb Write docs for void API module 2023-01-04 16:10:13 +11:00
Dion Moult fd8774718e Write docs for unit API module 2023-01-04 16:10:13 +11:00
Dion Moult 90d4d5afda Write docs for type API module 2023-01-04 16:10:12 +11:00
Dion Moult 92623b98e2 Write docs for system API module 2023-01-04 16:10:12 +11:00
Dion Moult b86818ead3 Write docs for style API module 2023-01-04 16:10:12 +11:00
Dion Moult 9e45435024 Documentation example fixes where I forgot to give things with representations a placement 2023-01-04 16:10:12 +11:00
Sigma Dimensions 981b6cd65f Various 4D improvements:
- refactor work schedule visualisation operator
- Fix text handler
- Improve durations UI for task times (proposal to replicate for all durations)
- Simplify durations ( P32H as P4D or P1D8H)
- Implement years/months in various datetime calculations
- Feature to customise animation colors per construction task type
- Feature to add Visual task bars
- Feature to customise Visual task bar material colors
- Move helper functions from blenderbim.bim.module.sequence to ifcopenshell.util.sequence
2023-01-04 02:08:52 +01:00
Dion Moult 0c77fb81ce Write documentation for structural API module 2023-01-03 17:32:08 +11:00
Dion Moult e53a9cd66c Write documentation for spatial API module 2023-01-03 17:31:56 +11:00
Dion Moult 2805109cef Write documentation for sequence API module 2023-01-03 17:31:48 +11:00
Dion Moult 6594445fa3 Add new code example for creating a work schedule 2023-01-03 17:31:23 +11:00
Massimo Fabbro bac6c62073 Finish mapping quantities (see #2533) (#2661)
* Finish Qto_SignBaseQuantities

* Finish Qto_RailBaseQuantities

* Finish Qto_PitorialSignQuantities

* Finish Qto_JunctionBoxBaseQuantities

* Finish Qto_ArealStratumBaseQuantities

* Finish Qto_SiteBaseQuantities

* Finish Qto_RoofBaseQuantities

* Finish Qto_RoofBaseQuantities

* Finish Qto_ChimneyBaseQuantities

* Finish Qto_EarthworksCutBaseQuantities

* Finish Qto_KerbBaseQuantities

* Finish Qto_PavementBaseQuantities

* Finish Qto_RampFlightBaseQuantities

* Finish Qto_SurfaceFeatureBaseQuantities

* Finish Qto_StairFlightBaseQuantities

* Finish Qto_ConstructionMaterialResourceBaseQuantities

* Finish Qto_DistributionChamberElementBaseQuantities

* Finish Qto_CourseBaseQuantities

* Finish Qto_RailingBaseQuantities

* Finish Qto_SleeperBaseQuantities

* Finish Qto_TankSurfaceQuantities

* Finish Qto_BuildingElementProxy

* Finish Qto_MemberBaseQuantities

* Finish Qto_BodyGeometryValidation

* Finish Qto_VolumetricStratumBaseQuantities

* Finish Qto_SpatialZoneBaseQuantities

* Finish Qto_ReinforcingElementBaseQuantities

* Finish update calculate all quantities button with new calculating functions
2022-12-30 21:33:18 +01:00
Thomas Krijnen 6180d73f75 Fix build-all collada compile on gcc 9.4.0 2022-12-28 13:33:16 +01:00
Sigma Dimensions 599c391407 code clean up for sequence module 2022-12-26 22:48:51 +01:00
Sigma Dimensions 1f18c5ab82 features to guess schedule date range and setup default task columns, and code clean up 2022-12-26 18:30:25 +01:00
Sigma Dimensions 2bb4e645b2 Fix: When counting working days, consider if calendar is applicable 2022-12-26 17:19:57 +01:00
Sigma Dimensions 5326e21fef 2 Fixes for 1 commit:
- Consider all tasks descendants when deriving task earliest or latest dates
2022-12-26 17:18:33 +01:00
Sigma Dimensions 891b49ff6f BBIM: feature to display total task Assignements 2022-12-26 13:38:54 +01:00
Thomas Krijnen 0f9c84c1ce Feature: express rules (#2662)
* temp reasonable state, missing rule_head contents

* better state, with init() and rule_id, but some missing parameters

* performance; embed root in dict; fix lookup

* Add ast_utils

* Fixes related to listnode and typo

* First steps with code gen

* Finalize first steps towards working code generation

* Backwards compat

* Rule execution, error formatting, test cases

* Where rule: support indexing, more expressions, test plane angle measure

* positive length measure test cases

* Venture into entity rules, test actor role

* Fixed for not unary op and address test cases

* Test group qualifier and air terminal type

* typeof() implementation and test annotation curve occ

* Flexible handling of expression, type set intersection, IfcAnnotationSurface test cases

* Functions, derived attrs, fix branch order, allow to terminate branches, apply -1 to index

* Implement express query(), test cases for 2x3 bspline curve

* Function args, if-else, repeat, entity instance construction, cshape test cases

* Multiple function call arguments and other small but influential changes. IfcDotProject() almost working except for variable case

* Make lowercase and fix assignment to qualified lhs

* Entity instance schema without file, aggregate assignment workaround, range fix, lowercase instance locals, extruded area direction test cases

* Enumeration item handling, query() robustness, arbitrary profile test cases

* Empty aggregate_initializer, xor and instance equality, nested expression fix in IfcCrossProduct, axis2-3d test cases

* Numeric stable sort with indices greater then 10, get ast node parent, redeclared derived support, nested expression robustness, define entity functions, case statements, xor, mod, bool literals, nvl, unknown, conversion based unit test cases

* Safeguard for schema name case norm, case norm query variable_id

* free instance comparison, supertype serialization fix, include inherited attributes in locals, proper elif-else in case stmt, loindex, shape rep test cases

* Union operator on set, retain general_aggr_types in parse tree, test cases for property set

* Escape stmt, rule locals and statements

* Return calculated values for redeclared derived attributes

* fix for rules without stmt, add derived and inverse attributes to locals, filter unused locals, wrap exists() arg with lambda to catch indexerror, support repeated aggr init element, typeof() none check, enum namespace uppercase

* Regen 2x3 rules

* Rule test filter only on basename

* Update fixtures for compliance with full body of rules

* Add rule support to ifcopenshell.validate

* Implement usedin() function, test cases for IfcWallSC MLS

* blength

* Run code generation on all schemas

* Try to eliminate runtime pyparsing dep

* Try to eliminate runtime pyparsing dep
2022-12-26 11:29:32 +01:00
Sigma Dimensions 2ccf25f7eb Display Scheduled Resource Usage as a property within rersource tree 2022-12-26 07:04:11 +01:00
Sigma Dimensions 082a326e67 Enable displaying number of resources used by a task 2022-12-26 06:16:19 +01:00
Sigma Dimensions d33a7244a3 Task Output selection improvements - allow recursive nesting and add checkbox to toggle between nested and non-nested 2022-12-26 05:17:56 +01:00
Thomas Krijnen bdb352ab22 Update ci-py-only.yml 2022-12-25 17:36:31 +01:00
Thomas Krijnen 04fb7cdf9c Update ci.yml 2022-12-25 17:36:18 +01:00
Andrej730 16de2f2e7e Added mention of sverchok's copyrights 2022-12-23 09:24:26 +05:00
Andrej730 0b6c8fe83e Added base for ifc sverchok modifier
It's located in "Modifiers" section of the properties (the same way as IFC Array or IFC Stair).
It requires Sverchok addon to be enabled (it's lazy importing it when it's needed, so there is no new constant dependency).

What you can now do with it:
1) Add sverchok graphs to this modifier and store simple data from sverchok in IFC ("Update data from sverchok). The "output" node of the sverchok graph has to be type of "Viewer Draw" and it's name should start with "ifcoutput" (no case-sensitive).
2) Import/export json of sverchok graphs.
2022-12-21 20:10:39 +05:00
Andrej730 d11959d303 Removed deprecated angle annotations operator
As it's currently completely replace with new more intuitive operator.
2022-12-19 20:13:59 +05:00
Andrej730 2bd5f3bbb3 Fixed annotations angle operator svg rendering bug
Fixed bug where the angle was drawn in svg only for the first two edges of the curve.
Now it takes into account all edges - first angle = edge0 + edge1, second angle = edge1 + edge2 etc.
2022-12-19 20:07:29 +05:00
CyrilWaechter cabc91dd5e Allow to write to unexisting directory
`ifcopenshell.write("/unexisting/path/model.ifc")` was failing without
throwing an error. It now works and a test ensure it works.
2022-12-19 01:44:40 +01:00
Sigma Dimensions 88c9f15320 fix library import for date utils 2022-12-18 04:34:20 +01:00
Sigma Dimensions b206e90097 Proposal to use parent productivity to calculate resource work. 2022-12-18 04:17:17 +01:00
Sigma Dimensions 73d3e9589c BBIM: forgotten commit for "ScheduleDuration" formatting 2022-12-18 03:56:10 +01:00
Sigma Dimensions 84a07c3fdf fix typo for resource_work calculation 2022-12-18 03:41:13 +01:00
Sigma Dimensions ea1c306733 fix offset date calculation when finish date lies outside of calendar range 2022-12-18 03:34:20 +01:00
Dion Moult 30dc13f1a7 Add support for loading IFCs with aggregated types 2022-12-17 20:48:05 +11:00
Dion Moult 9b3b06adf3 Write documentation for root API module 2022-12-16 15:29:50 +11:00
Dion Moult 436482532d Write documentation for resource API module 2022-12-16 15:07:02 +11:00
Dion Moult d2fee728f2 Write documentation for pset_template API module 2022-12-16 11:22:46 +11:00
Dion Moult 0ccea3cef8 Add documentation for pset API module 2022-12-15 16:02:27 +11:00
Dion Moult 241406f676 Consolidate standard "clean environment" instructions in docs 2022-12-14 23:13:01 +11:00
Dion Moult a89c061b58 Write docs for project API module 2022-12-14 17:28:34 +11:00
Dion Moult 1211c153d9 Revert detailed SPF comments in code example
Sorry I find these really hard to read an intimidating for new users.
2022-12-14 17:28:12 +11:00
Dion Moult a5bdcf488b Write docs for profile API module 2022-12-14 16:49:06 +11:00
Dion Moult a5a1ef2e8e Write docs for owner API module 2022-12-14 15:58:53 +11:00
Ryan Schultz 05cd11ae12 Added some troubleshooting measures to determine if another plugin is interfering with BB. 2022-12-13 08:12:21 -06:00
fbpyr 307f9a2322 bump python version
bumping python version, so it works with our ubuntu latest image and `actions/setup-python@v2`
2022-12-13 21:35:42 +11:00
CyrilWaechter 3d05dc234a Ignore mypy cache, swig and compiled files 2022-12-11 22:49:44 +01:00
CyrilWaechter 1f87165601 Remove bloat line 2022-12-11 22:46:05 +01:00
CyrilWaechter a513e1b56a Remove duplicate line 2022-12-10 19:45:17 +01:00
CyrilWaechter ec0c8f509e Move boundary origin to space origin
Ensure that boundary origin is same as space origin to assign correct
geometry into IFC.
2022-12-10 19:44:28 +01:00
Ryan Schultz a23bac6ecb where to look to see if a dependency has changed. 2022-12-09 10:31:57 -06:00
Kristoffer Andersen 569c19abca Merge pull request #2642 from CyrilWaechter/v0.7.0
Remove unecessary line which make build failing
2022-12-09 10:52:01 +01:00
CyrilWaechter dfeac601e4 Remove unecessary line which make build failing 2022-12-09 09:44:19 +01:00
Dion Moult 53cecb3546 Write documentation for the nest API module. 2022-12-07 18:56:44 +11:00
Dion Moult cf87852d9c Add API documentation for the material module 2022-12-07 17:30:08 +11:00
Dion Moult 7da3d9ee70 Add docs for library API module 2022-12-07 12:20:29 +11:00
Dion Moult 9650bbb5ee Add docs to layer API 2022-12-07 08:59:24 +11:00
Dion Moult 45953c6b49 Add API documentation for group module 2022-12-06 16:42:47 +11:00
Dion Moult b34caa1b5f Add documentation for grid API module 2022-12-05 17:10:22 +11:00
Ryan Schultz ea8433b187 Added more examples to the Code Examples page 2022-12-05 15:44:38 +11:00
Andrej730 390f23a30c Added ReloadSelectedIfcFile operator
Added ReloadSelectedIfcFile operator and added it to IFC Quality Control -> IFC Debug, it's useful when you're debugging and doing a lot of iterations on the same file.
2022-12-05 14:59:58 +11:00
Massimo Fabbro c8ba2b53c9 Mapping quantities (see #2533) (#2627)
* Finish mapping Qto_PileBaseQuantities

* Finish mapping IfcPipeSegmentBaseQuantities

* Finish mapping IfcVehicleBaseQuantities

* Finish mapping Qto_OpeningElementBaseQuantities

* Finish mapping Qto_PlateBaseQuantities

* Finish Qto_MarineFacilityBaseQuantities

* Finish Qto_FacilityPartBaseQuantities

* Finish Qto_ProjectionElementBaseQuantities

* Better syntax
2022-12-02 18:19:43 +01:00
Thomas Krijnen dbbbb52e9e #2624 2022-12-02 13:43:44 +01:00
Dion Moult 052d6e2254 Add API docs for georeferencing module 2022-11-28 10:30:51 +11:00
Massimo Fabbro b05c711fb9 Merge pull request #2619 from maxfb87/mapping_quantities
Finish mapping quantities Qto_SpaceBaseQuantities (see #2533)
2022-11-27 08:17:32 +01:00
CyrilWaechter eb0322759c Allow ifcpatch to work with .ifcZIP | .ifcXML
Closes #2350
2022-11-26 05:37:00 +01:00
CyrilWaechter 25a7a95284 Allow to write .ifcZIP | .ifcXML #2350
Transparently write any .ifc | .ifcXML | .ifcZIP formatted file
 with any extension :
- by guessing format from its extension
(eg. .zip | .ifczip | .ifcZIP | .ifcXML | .ifcxml | .IFC | .ifc)
zipped file can file can be of any supported format
- by specifying a format
- by specifying if it should be zipped
2022-11-26 05:19:01 +01:00
Ryan Schultz 8ec1b7bb3e misspellings with outer_boundary 2022-11-25 12:29:00 -06:00
CyrilWaechter 5f094f6a95 Fix IfcSystem assignment bug introduced in f0b1935
Fixing my own bug…
Note : always run all test before pushing not only the one which used to fail…
2022-11-25 19:01:07 +01:00
CyrilWaechter f0b1935861 Add system assignment check
@aothms Is there a way to check against IFC formal propositions ?
Example for [IfcZone](http://ifc43-docs.standards.buildingsmart.org/IFC/RELEASE/IFC4x3/HTML/lexical/IfcZone.htm#5.4.3.82.4-Formal-propositions)
@Moult Should I move is_assignable to an util ?
2022-11-25 18:52:58 +01:00
Massimo Fabbro 2540ba3467 Finish mapping quantities Qto_SpaceBaseQuantities 2022-11-25 17:22:13 +01:00
CyrilWaechter 88093728a7 Add IfcSpatialElementType to assignable classes
Required for IFC4+ to assign IfcSpaceType and IfcSpatialZoneType
Solves #2615
2022-11-25 15:55:32 +01:00
Thomas Krijnen af6734d7b0 IfcOpenShell WASM module build script (#2617) 2022-11-25 13:16:36 +01:00
Thomas Krijnen 8a108f37c3 Moar schema indices 2022-11-24 09:36:02 +01:00
Andrej730 d88510b919 Added new angle annotations operator
New operator should be more intuitive to work with. I kept the old one too for now.
2022-11-24 12:05:54 +06:00
Thomas Krijnen afe9f7602f Test cases for #2586 2022-11-23 13:46:15 +01:00
Thomas Krijnen ff2b79a41f Distinguish between nil and derived in validate.py 2022-11-23 13:45:44 +01:00
Thomas Krijnen 631722303f https://raw.githubusercontent.com/buildingSMART/IFC4.3-html/sep-13-release/IFC/RELEASE/IFC4x3/HTML/IFC4X3_TC1.exp 2022-11-23 13:23:49 +01:00
Dion Moult 4b20a407e9 Add API docs for drawing module 2022-11-23 11:23:40 +09:00
Dion Moult b23f445946 Add API documentation for document module 2022-11-23 10:56:41 +09:00
CyrilWaechter 09c8268494 Fix path for ci (root at src/ifcopenshell-python) 2022-11-22 23:25:11 +01:00
CyrilWaechter 0bc3543914 Update test/input submodule to https://github.com/IfcOpenShell/files/commit/20a17fcf3b1be2bc48af0273f7604af5b7cc4068
#2350
2022-11-22 21:23:26 +01:00
CyrilWaechter 0412eb48a0 Fix type hints #2350 for python <3.10 2022-11-22 01:00:04 +01:00
CyrilWaechter e7d5add13d Add test suite for ifcopenshell.open #2350
Fix open function: format argument was unused
2022-11-22 00:40:27 +01:00
Thomas Krijnen 25948a608a Missing parse updates after 4e0a19e 2022-11-21 09:43:13 +01:00
Dion Moult 11f772b5a3 Add documentation for cost module 2022-11-21 12:05:58 +09:00
CyrilWaechter 412cf7c6cb Add stable AUR package 2022-11-21 01:42:26 +01:00
CyrilWaechter b2d5580c37 Allow to open .ifcZIP | .ifcXML #2350
Transparently open any .ifc | .ifcXML | .ifcZIP formatted file
 with any extension :
- by guessing format from its extension
(eg. .zip | .ifczip | .ifcZIP | .ifcXML | .ifcxml | .IFC | .ifc)
zipped file can file can be of any supported format
-  by specifying a format
2022-11-21 01:32:12 +01:00
CyrilWaechter 8386aa0e0a IfcZone: Allow to remove an object from an IfcZone 2022-11-20 17:48:30 +01:00
CyrilWaechter ae0f2a9de6 IfcZone : Allow basic selection/deletion/creation 2022-11-20 17:35:27 +01:00
Thomas Krijnen 4e0a19e013 https://raw.githubusercontent.com/buildingSMART/IFC4.3-html/v4.3.1.0/IFC/RELEASE/IFC4x3/HTML/IFC4X3_ADD1.exp 2022-11-20 16:52:46 +01:00
Thomas Krijnen 9a840174d2 Fix test case 2022-11-20 14:38:17 +01:00
Massimo Fabbro 83528ba80a Merge pull request #2612 from maxfb87/Mapping_quantities
Finish mapping Qto_CoveringBaseQuantities (see #2533)
2022-11-19 23:05:29 +01:00
Massimo Fabbro eb23372fd0 Finish mapping Qto_CoveringBaseQuantities 2022-11-19 21:44:51 +01:00
Massimo Fabbro 286e9dfda9 Merge pull request #2605 from maxfb87/mapping_quantities
Mapping quantities (see #2533)
2022-11-19 17:28:29 +01:00
Vukas Pajic f0b9c9ad7b #2602 get_properties no longer throws error when EnumerationValues = None 2022-11-18 14:35:57 +01:00
Kristoffer Andersen afd79ee16d bump conda daily build number 2022-11-18 13:52:35 +01:00
Ryan Schultz caa4445eb0 Added diffuse_color inside IFC Style panel 2022-11-18 12:36:10 +11:00
Andrej730 f850bd5741 documented create_simple_2dcurve() 2022-11-17 12:03:28 +06:00
Massimo Fabbro 8c5883ffaa Finish mapping Qto_DoorBaseQuantities and Qto_WindowBaseQuantities 2022-11-16 22:28:10 +01:00
Massimo Fabbro 8f8940de2a Minor fix 2022-11-16 21:56:29 +01:00
Massimo Fabbro bf59871db5 Fix get_gross_side_area for Qto_WallBaseQuantities 2022-11-16 21:50:52 +01:00
Dion Moult 22eb0bf2e4 Write API documentation for control module 2022-11-16 18:38:51 +09:00
Dion Moult 68838a37e5 Add API documentation for context module 2022-11-16 18:18:22 +09:00
Dion Moult 22b4bfe07d Add API docs for constraint module 2022-11-16 08:57:25 +09:00
Andrej730 ff2714d991 Added c-, z-profiles with lips to AU steel library
Added c- and z-profiles with lips to IFC AU Steel profile library (based on data from https://github.com/boltsparts/boltsparts/pull/15).

For z-profiles I've created simple 2D curve generation function that can be reused later - it requires list of points and list of points with fillets and the results are list of curve points, segments and IfcIndexedPolyCurve.

Example:

    points, segments, ifc_curve = create_simple_curve_from_coords(coords,
        fillets =     (0,   1,   4, 5, 6,   7,   10, 11),
        fillet_radius=(r+t, r+t, r, r, r+t, r+t, r, r),
        closed=True, ifc_file=ifc_file)
2022-11-15 13:35:44 +06:00
Dion Moult 308ee7f7a9 Add API documentation for classification module 2022-11-15 16:11:47 +09:00
Dion Moult c87924c4ba Add API docs for boundary module 2022-11-15 08:27:26 +09:00
Thomas Krijnen 8f33c2ca9d Missing file 2022-11-14 09:38:51 +01:00
Thomas Krijnen 9351038009 Refactoring 2022-11-14 09:23:59 +01:00
Dion Moult 66281ec1f1 Add docs for attribute API 2022-11-14 07:55:50 +11:00
Dion Moult ae843e6faf Add docs for aggregate api 2022-11-14 07:48:20 +11:00
Massimo Fabbro 5ca0a254ff Cleaning around in qto_calculator 2022-11-14 06:50:33 +11:00
Thomas Krijnen c18458b3db Refactoring 2022-11-13 11:21:58 +01:00
Thomas Krijnen 3736a8b4b4 Refactoring 2022-11-13 11:04:16 +01:00
Dion Moult 6558f68cd4 Minor fix 2022-11-13 15:32:24 +11:00
Dion Moult 4fd5806f70 Minor documentation tweaks 2022-11-13 15:30:35 +11:00
Thomas Krijnen b1c730c04d draw.include_projection setting 2022-11-12 15:49:29 +01:00
Dion Moult bdec443410 Minor fixes to IfcTester 2022-11-12 14:08:01 +11:00
Erik Abrahamsson d9afb0f8a2 Fix translation of days in Powerproject importer 2022-11-12 08:32:19 +11:00
Andrej730 9dfd7e03f7 IFC Stair - added "Wood / Steel" stair type
Added new stair type "Wood / Steel" to IFC Stair modifier. It's less sophisticated than "Concrete" stair type - it acts more like a "Dumb stair" operator we have.
2022-11-11 17:32:57 +06:00
Andrej730 31b633b8b6 IFC Stair - cosmetic adjustments 2022-11-11 16:22:55 +06:00
Massimo Fabbro 20f4737ffd Fix some errors and update notes 2022-11-11 12:00:08 +11:00
Massimo Fabbro 4d4ab63404 Finish mapping Qto_BeamBaseQuantities 2022-11-11 12:00:08 +11:00
Massimo Fabbro 056f7610c1 Fix some get_lateral_area function errors 2022-11-11 12:00:08 +11:00
Massimo Fabbro bfb2f06e44 Finish mapping Qto_BeamBaseQuantities 2022-11-11 12:00:08 +11:00
Dion Moult a7febaa9b0 Fix astapowerproject import to ifc which seemed mostly broken for some reason 2022-11-11 11:38:09 +11:00
Sigma Dimensions ff984f62af BBIM feature to load nested tasks outputs, and select all nested task outputs 2022-11-10 18:05:26 +01:00
Otso Alho d005524c4c catch missing instances one by one. do not throw away whole relation. 2022-11-10 14:18:50 +01:00
Dion Moult a2362668a3 The profile manager now only manages named profiles 2022-11-10 23:32:15 +11:00
Andrej730 277317e6fd Added "IFC Stair" to object modifiers
1) Added "IFC Stair" to object modifiers - it works in similar way as "IFC Array". It creates a stair from current object (if it's IfcStairFlight) and writes it's parameters to property set "BBIM_Stair" and also to IfcStairFlight parameters.
Then you can edit it - modifying stair parameters and immediately see the changes, then you can save those changes or discard them.
2) "Clever Stair" operator (in Shift+A menu) works a bit different now - it creates new IfcStairFlight with IFC Stair modifier activated.
2022-11-10 15:57:08 +06:00
Dion Moult b3f0b9b37b Get cross section area from profile directly rather than mesh parsing 2022-11-10 17:37:14 +11:00
Dion Moult 6de1e73e4f Distinguish between calculating gross and net surface area 2022-11-10 17:02:11 +11:00
Thomas Krijnen c6cdf4ab77 Be a bit more careful in throwing NoSuchObject in case of triangulated face handling https://github.com/IfcOpenShell/wasm-preview/issues/1 2022-11-09 20:32:53 +01:00
Dion Moult 21f8aca00f Elementary fixes to the BCF module just so that things load (but lots are still broken) 2022-11-09 23:35:47 +11:00
Thomas Krijnen 1d6c3b3fc2 Update build-all.py 2022-11-09 10:58:38 +01:00
Thomas Krijnen 4362163ea9 Update build-all.py 2022-11-09 10:57:42 +01:00
Dion Moult 67808b8384 Fix issue where sometimes world coordinate system would trigger the georeferencing distance threshold 2022-11-09 18:37:16 +11:00
Andrea Ghensi 2cbc7a10ab [BCF] New BCF XML handler with better performances (#2355)
BREAKING CHANGE: incompatible methods and objects with previous version.

closes #2321
2022-11-09 08:54:23 +11:00
Massimo Fabbro d97594df96 Finish Qto_SlabBaseQuantities quantities (#2567) 2022-11-08 22:21:00 +11:00
Dion Moult c7eca135de Document how you can get the matrix of elements from IfcOpenShell Python. 2022-11-08 20:18:08 +11:00
Dion Moult e63ec83325 Fix some bugs in IfcTester that prevented tests running on IFC2X3 2022-11-08 20:05:56 +11:00
Dion Moult 1ddae8a6f5 Fix #2577. Bug where system editing UI appears multiple times. 2022-11-08 20:04:59 +11:00
Andrej730 03781bdf9b Updated EU, AU steel profiles libraries
1) Updated AU profiles based on more correct data (https://github.com/boltsparts/boltsparts/pull/4) - also added welded i-beam profiles
2) Added inner and outer fillet radius to hollow profiles since this data was added to https://github.com/boltsparts/boltsparts
3) Added more data to ifc profiles that was available on bolts: FilletRadius for I-beams, C-beams; FilletRadius and EdgeRadius for I-beams. Sovles #2556
4) Set names IfcProfileDef names too. Solves #2556
2022-11-08 11:29:43 +06:00
Dion Moult 62e5c3684b Fix #2564. Bug where profiles were not reused across copies. 2022-11-07 22:37:53 +11:00
Dion Moult ff90728a22 New unassign container API function 2022-11-07 18:47:15 +11:00
Dion Moult dadf23681b Fix #2123. Bug where mesh to extrusion conversion didn't work for thin extrusions. 2022-11-07 17:45:05 +11:00
Dion Moult 516bd37518 Fix #2550. Bug where material styles weren't included when appending from a library 2022-11-07 16:20:23 +11:00
Dion Moult 521ec1b124 Support styles defined somewhere along a boolean result stack 2022-11-07 11:24:13 +11:00
Thomas Krijnen 4881e4c686 Error in type checking introduced in ca98e77b40 2022-11-06 14:45:00 +01:00
Thomas Krijnen a57cac5f9c #2558 make conditional 2022-11-06 14:39:01 +01:00
Thomas Krijnen ca98e77b40 #2471 Fix refcount when setting aggregates 2022-11-05 16:24:04 +01:00
Dion Moult cbd9c898c3 Fix failing tests 2022-11-04 17:08:41 +11:00
Massimo Fabbro f57731a10a Mapping quantities #2533 (#2565)
* Implement some Qto_WallBaseQuantities quantities

* finish Qto_WallBaseQuantities
2022-11-04 08:56:38 +11:00
Dion Moult 0142bface5 You can now edit parameterised profiles again in the material panel 2022-11-04 00:05:09 +11:00
Dion Moult 5acbf4658f Material UI now prompts to add a profile if none exist in your library 2022-11-03 23:44:47 +11:00
Dion Moult 32e00f6d49 Refactor material UI to not depend on IOS API data class 2022-11-03 23:36:16 +11:00
Gorgious56 4e3ce192cc Custom property set attributes now show their description when right clicking the field
+ Add explicit numerical constraint members to attribute class #2562
+ Fix error in numerical value callback #2562
+ Fix error in setting attribute values because of wrong type cast
2022-11-03 09:54:35 +01:00
Gorgious56 841bb59d18 Fix error when the user selects a class suggestion that is not a predefined type 2022-11-03 08:21:55 +01:00
Gorgious56 8dd8a23af9 The predefined type suggestions now fetch their values from the ifcopenshell.util.doc module instead of parsing the file data 2022-11-03 08:16:40 +01:00
Dion Moult adc59de2bf Minor fix to accommodate null descriptions and parents with no attributes
# Conflicts:
#	src/ifcopenshell-python/ifcopenshell/util/doc.py
2022-11-03 17:13:56 +11:00
Gorgious56 20198bccdb Numerical Attribute validation callback (#2562)
* Numerical attributes can now be clamped

* #2545 : IfcMaterialLayer Priority is now clamped between 0 and 100

* Run black
2022-11-03 14:41:47 +11:00
Andrej730 c0909dace0 black.py formatting 2022-11-02 17:35:51 +06:00
Andrej730 bd054d4baf Added more advanced stair generating operator 2022-11-02 17:31:27 +06:00
Dion Moult 35a70a4ae3 Fix #2559. Don't consider door and window styles in IFC4X3. 2022-11-02 17:31:10 +11:00
Dion Moult a1d30d157b Minor fix. 2022-11-02 17:27:18 +11:00
Kristoffer Andersen 621d89c80e Update ci-ifcopenshell-conda-daily.yml
Bump GA versions away from deprecated versions
2022-11-01 20:58:59 +01:00
Gorgious 2fa3793622 Selecting an ifc predefined type in addition to the ifc class in the object ifc class selector now correctly assigns it 2022-11-01 17:34:29 +01:00
Kristoffer Andersen 872c057c4f bump conda daily to python v 3.10 and 3.11 2022-11-01 16:16:28 +01:00
Kristoffer Andersen f6a530e5e3 remove cap on boost version 2022-11-01 15:17:32 +01:00
Dion Moult 2043a629c7 Material profile sets UI cleanup and now lets you choose from a profile in your library instead of creating new profiles on the fly all the time 2022-11-01 23:39:07 +11:00
Kristoffer Andersen a7f9d548cf fix: add conditional #include <windows.h> for certain files breaking … (#2558)
* fix: add conditional #include <windows.h> for certain files breaking with boost v1.78+

* Revert "fix: add conditional #include <windows.h> for certain files breaking with boost v1.78+"

This reverts commit 7cd5c6292b.

* fix: add only `BOOST_REGEX_NO_W32` to fix issues boost v1.78+
2022-11-01 13:30:07 +01:00
Dion Moult 1391dd38ec Update README.md 2022-11-01 13:29:20 +01:00
Dion Moult 38f6b7aca0 Clean up README
This new proposed readme makes it more obvious to readers that there is a large collection of tools available, a brief glance of what they do, and directs users to the dedicated websites and documentation for further instructions on installation and so on.
2022-11-01 13:29:20 +01:00
Dion Moult f0e4397ef9 Change PyPI package readme. See #2531. 2022-11-01 09:08:13 +11:00
Dion Moult 6b7ffc8ca6 Merge pull request #2544 from Gorgious56/Tooltips
Blenderbim : Implement tooltips and online docs in the interface
2022-11-01 08:46:51 +11:00
Gorgious 5458f4f9dd Slightly optimise the display of the description popup 2022-10-31 22:22:46 +01:00
Gorgious 0c3269d9e1 Revert "Squashed commit of the following:"
This reverts commit f07cf1d1ef.
2022-10-31 19:53:19 +01:00
Gorgious 84dde4c5c0 Property set templates attributes now show a tooltip on hover 2022-10-31 19:34:59 +01:00
Gorgious 7eefb3f62f Pset nominal values now show their description on right click 2022-10-31 19:09:58 +01:00
Gorgious 90369bf7f7 Directly use predefined type doc function instead of wrapper 2022-10-31 18:10:03 +01:00
Gorgious eff3318399 Directly use attribute doc functions instead of wrapper 2022-10-31 18:01:59 +01:00
Gorgious ee59d366dc Directly use entity doc functions instead of wrappers 2022-10-31 17:57:24 +01:00
Gorgious c0b91848a3 Refactor right click context menu 2022-10-31 17:30:33 +01:00
Gorgious d05d430642 Directly use pset doc functions instead of wrapped ones 2022-10-31 17:30:18 +01:00
Gorgious e8a73e008e Pset types and template types enums now show tooltips on hover 2022-10-31 17:15:58 +01:00
Thomas Krijnen 259bb4f36a Let's try #2551 2022-10-31 13:39:01 +01:00
Dion Moult b87191a358 Forgot to commit new profile import export in model tool 2022-10-31 22:34:48 +11:00
Gorgious a359230181 Actually fix #2553 : Add safeguard to unregister custom right click context menu 2022-10-31 11:29:04 +01:00
Gorgious a95e12c0b2 Pset enums now display a tooltip on hover 2022-10-31 11:09:31 +01:00
Gorgious 2ac27c6fdd Add additional line to the ifc description popup to display the attribute type 2022-10-31 10:43:50 +01:00
Gorgious 757578a74b Fix error fetching the description of a nameless attribute 2022-10-31 10:34:18 +01:00
Gorgious 3466a38a44 Removed hardcoded descriptions thanks to 937f39c 2022-10-31 10:32:49 +01:00
Gorgious f07cf1d1ef Squashed commit of the following:
commit 7fb4cdaa16
Author: Gorgious <nathan.hild@gmail.com>
Date:   Mon Oct 31 09:35:06 2022 +0100

    Fix #2546 : Editing a material now automatically ends the edition of its material set item if necessary

commit 427411e758
Author: Dion Moult <dion@thinkmoult.com>
Date:   Mon Oct 31 19:04:57 2022 +1100

    You can now edit arbitrary profiles in the profile manager

commit cc056d5d53
Author: Gorgious <nathan.hild@gmail.com>
Date:   Mon Oct 31 08:52:11 2022 +0100

    Fix #2553 : Right click documentation is now only available for Blender 3.3+
    Also fix an error in the console when right clicking a button

commit 31600033bf
Author: ArturTomczak <artomczak@gmail.com>
Date:   Sun Oct 30 23:16:08 2022 +0100

    add BCF reporter to IDS (#2552)

    * grammar

    * reason not always a list

    * extend readme with instruction

    * remove microseconds from date

    * add BCF reporter

commit 0338825175
Author: Dion Moult <dion@thinkmoult.com>
Date:   Sun Oct 30 23:26:59 2022 +1100

    You can now add new profile definitions in the profile manager

commit d9f8aa4e7b
Author: Andrej730 <azhilenkov@gmail.com>
Date:   Sun Oct 30 11:29:42 2022 +0600

    Added ifc types schema for Ifc2x3 and Ifc4

    Types schema located in separate json file, it's structure:
    { ifc_type_name: {"description": type_description, "spec_url": type_spec_url} }

    I've also added new API function to get type data - `get_type_doc(version, ifc_type)`

commit 6803788676
Author: Dion Moult <dion@thinkmoult.com>
Date:   Sun Oct 30 11:02:18 2022 +1100

    Load thumbnails in a paginated manner to allow to scale to larger models

commit d421cc0bbb
Author: Massimo Fabbro <79401028+maxfb87@users.noreply.github.com>
Date:   Sun Oct 30 00:07:56 2022 +0200

    No mapped quantities in qto calculator  has None value (#2549)

commit bf57c8b1bf
Author: Dion Moult <dion@thinkmoult.com>
Date:   Sat Oct 29 23:45:04 2022 +1100

    Refactor profile module to use data class

commit 9a25db6584
Author: Andrej730 <azhilenkov@gmail.com>
Date:   Sat Oct 29 18:31:40 2022 +0600

    get_attribute_doc get_entity_doc now use recursive method by default

commit 17ccd0adf7
Author: Andrej730 <azhilenkov@gmail.com>
Date:   Sat Oct 29 18:27:51 2022 +0600

    Simplified getting entity parent information for schema api

    Removed unnecssary parent information from json schema - now API is getting it from ifcopenshell schema.

commit 937f39c898
Author: Andrej730 <azhilenkov@gmail.com>
Date:   Sat Oct 29 17:35:43 2022 +0600

    Added information about parent entity to schema

    Added information about parent entity to schema (it's located in "parent_entity" entity parameter inside schema), both for Ifc2x3 and Ifc4.

    Modified get_entity_doc and get_attribute_doc - now you can use additional optional parameter `recursive=True` with those functions - then parent entities attributes will be included to the list of entity's attributes. For example IfcWindow will also have attributes from IfcBuildingElement, IfcElement, IfcProduct etc...

commit 0b5ccd88a5
Author: Andrej730 <azhilenkov@gmail.com>
Date:   Sat Oct 29 16:31:54 2022 +0600

    Added descriptions for psets and qsets to ifc2x3, ifc4 schema

    Example of getting pset description: `get_property_set_doc("IFC4", "Pset_ZoneCommon")['description']`

commit ebf3cd8909
Author: Andrej730 <azhilenkov@gmail.com>
Date:   Sat Oct 29 10:32:26 2022 +0600

    Added IFC libraries for AU and EU profiles

    Also added script that was used to generate those libraries (the script is using data from https://github.com/boltsparts/BOLTS).

    Types of steel profiles included:
    I profiles, Z profiles, C profiles (known as U profiles in IFC), L profiles (both equal and unequal), hollow profiles (circle, square and rectangular).

commit 08afec3461
Author: Dion Moult <dion@thinkmoult.com>
Date:   Fri Oct 28 22:32:15 2022 +1100

    Support material layer set usage negative direction sense for parametric walls

commit 4408a23873
Author: Dion Moult <dion@thinkmoult.com>
Date:   Fri Oct 28 22:30:43 2022 +1100

    Merge duplicate types IfcPatch recipe can now merge based on any attribute
2022-10-31 09:51:05 +01:00
Gorgious 7fb4cdaa16 Fix #2546 : Editing a material now automatically ends the edition of its material set item if necessary 2022-10-31 09:35:06 +01:00
Dion Moult 427411e758 You can now edit arbitrary profiles in the profile manager 2022-10-31 19:05:04 +11:00
Gorgious cc056d5d53 Fix #2553 : Right click documentation is now only available for Blender 3.3+
Also fix an error in the console when right clicking a button
2022-10-31 08:52:11 +01:00
ArturTomczak 31600033bf add BCF reporter to IDS (#2552)
* grammar

* reason not always a list

* extend readme with instruction

* remove microseconds from date

* add BCF reporter
2022-10-31 09:16:08 +11:00
Dion Moult 0338825175 You can now add new profile definitions in the profile manager 2022-10-30 23:27:11 +11:00
Andrej730 d9f8aa4e7b Added ifc types schema for Ifc2x3 and Ifc4
Types schema located in separate json file, it's structure:
{ ifc_type_name: {"description": type_description, "spec_url": type_spec_url} }

I've also added new API function to get type data - `get_type_doc(version, ifc_type)`
2022-10-30 11:33:20 +06:00
Dion Moult 6803788676 Load thumbnails in a paginated manner to allow to scale to larger models 2022-10-30 11:02:31 +11:00
Massimo Fabbro d421cc0bbb No mapped quantities in qto calculator has None value (#2549) 2022-10-30 09:07:56 +11:00
Dion Moult bf57c8b1bf Refactor profile module to use data class 2022-10-29 23:45:12 +11:00
Andrej730 9a25db6584 get_attribute_doc get_entity_doc now use recursive method by default 2022-10-29 18:31:40 +06:00
Andrej730 17ccd0adf7 Simplified getting entity parent information for schema api
Removed unnecssary parent information from json schema - now API is getting it from ifcopenshell schema.
2022-10-29 18:27:51 +06:00
Andrej730 937f39c898 Added information about parent entity to schema
Added information about parent entity to schema (it's located in "parent_entity" entity parameter inside schema), both for Ifc2x3 and Ifc4.

Modified get_entity_doc and get_attribute_doc - now you can use additional optional parameter `recursive=True` with those functions - then parent entities attributes will be included to the list of entity's attributes. For example IfcWindow will also have attributes from IfcBuildingElement, IfcElement, IfcProduct etc...
2022-10-29 17:35:43 +06:00
Andrej730 0b5ccd88a5 Added descriptions for psets and qsets to ifc2x3, ifc4 schema
Example of getting pset description: `get_property_set_doc("IFC4", "Pset_ZoneCommon")['description']`
2022-10-29 16:31:54 +06:00
Andrej730 ebf3cd8909 Added IFC libraries for AU and EU profiles
Also added script that was used to generate those libraries (the script is using data from https://github.com/boltsparts/BOLTS).

Types of steel profiles included:
I profiles, Z profiles, C profiles (known as U profiles in IFC), L profiles (both equal and unequal), hollow profiles (circle, square and rectangular).
2022-10-29 10:32:26 +06:00
Gorgious56 d9b1c26bf0 Fix bug when prop_with_search is used with a prop that is not an enum 2022-10-28 16:50:11 +02:00
Gorgious56 490da99792 Pset primary measure type should now display a tooltip on hover
Awaiting for ifc measure descriptions to be added to the docs
2022-10-28 16:32:17 +02:00
Gorgious56 528df00027 Organization and person now show a tooltip on hover 2022-10-28 16:30:04 +02:00
Gorgious56 bc10f934d6 Add tooltips to attributes to guide users to their description 2022-10-28 15:46:02 +02:00
Gorgious56 819db11db4 Simplify attribute description 2022-10-28 15:44:31 +02:00
Gorgious56 37a0854d40 Material set profile attributes now show a description on right click
+ added link to docs to Material set profile attributes and Material set item
2022-10-28 14:39:36 +02:00
Gorgious56 b984268b5d parameterized profile classes now shot a tooltip on hover 2022-10-28 14:34:57 +02:00
Gorgious56 e4d2ae185d Fix bug when an entity doesn't have any predefined type to fetch in the docs 2022-10-28 14:26:08 +02:00
Gorgious56 345098b4af Fix bug when attribute enum items descriptions are not filled in 2022-10-28 13:51:52 +02:00
Gorgious56 98b517954f Material layer set item attributes now show a description on right click 2022-10-28 13:47:42 +02:00
Gorgious56 dba8d2df9d Fix bug when right clicking an operator button in the interface 2022-10-28 13:43:41 +02:00
Gorgious56 6756ba7022 Ifc profile classes enum now shows a tooltip on hover 2022-10-28 13:42:51 +02:00
Dion Moult 08afec3461 Support material layer set usage negative direction sense for parametric walls 2022-10-28 22:32:15 +11:00
Dion Moult 4408a23873 Merge duplicate types IfcPatch recipe can now merge based on any attribute 2022-10-28 22:30:57 +11:00
Gorgious56 c33b5aa4b7 Extract attribute description fetching 2022-10-28 12:08:28 +02:00
Gorgious56 0ebbc62a06 Add popup window when clicking Ifc description in right click context menu 2022-10-28 11:54:07 +02:00
Gorgious56 0494373155 Store Ifc class in attribute
This shouldn't be necessary ? but it's easier for now
2022-10-28 11:53:23 +02:00
Gorgious56 c897cb3ea0 Show descriptions for attributes in right click context menu
This is hacky, looking for a solution to get tooltips on hover over field
2022-10-28 11:10:12 +02:00
Gorgious56 f38e8f26f5 You can now access property sets online documentation through the right click context menu 2022-10-28 09:07:33 +02:00
Gorgious56 7164103a80 Search button now only displays when an enum has more than 10 items
To reduce visual clutter for enums with only a few values
Also tweak the online docs ui function
2022-10-28 08:33:56 +02:00
Gorgious56 5fb7f33fb0 You can now access the ifc classes online docs from the right click context menu
+ Remove the ugly buttons in the interface
2022-10-28 08:19:35 +02:00
Gorgious56 e602f16ed3 Add description to more ifc entities enums
- Constraint type
- Material type
- ObjectMaterial type
- Actor class and type
- Structural load type and boundary condition type
- Style type
- System type
- Relating type
- Unit type
2022-10-27 17:19:24 +02:00
Gorgious56 17a872a8d8 Use new get_predefined_type_doc function #1982 2022-10-27 16:00:12 +02:00
Dion Moult 4533b74d65 Minor IfcTester fixes 2022-10-27 23:37:01 +11:00
Dion Moult 62229b032e You can now array IFC elements parametrically 2022-10-27 23:36:09 +11:00
Andrej730 f2c2fa8dce Added entities predefined types to schema
Both for ifc2x3 and ifc4 but there is still a problem with ifc2x3 predefined types description - it's empty since constants have no description in ifc2x3. For temporary solution I've used description available on ifc4 for ifc2x3 too.

I've also added API function to get entity's predefined type description: `get_predefined_type_doc(version, entity, predefined_type)`

Example for the same constant that has no description in ifc2x3 and has one in ifc4:
https://github.com/buildingSMART/IFC/blob/Ifc2.3.0.1/Constants/s/STEAMINJECTION_1b6zZZM99EevT2LVdQ7B%24V.xml
https://github.com/buildingSMART/IFC/blob/Ifc4.0.2.1/Constants/s/STEAMINJECTION_1b6zZZM99EevT2LVdQ7B$V.xml
2022-10-27 16:23:46 +06:00
Dion Moult ae170bbfac Fix #2541. Forgot that wall body must be based off the axis reference line. 2022-10-27 19:08:37 +11:00
Dion Moult 706b925f4c FM documents now use the identification as the key, and refer to the doc info instead of doc reference 2022-10-27 09:46:11 +11:00
Dion Moult 65288eac04 Minor fix. See #2533. 2022-10-27 09:45:28 +11:00
Massimo Fabbro bf5a9bdac6 Start quantity calculator implementation (related to #2533) (#2534)
* Start quantity calculator implementation

* Fix get qto_name
2022-10-26 23:51:52 +11:00
Dion Moult e22ebfbf94 Fix #2523. Extrusion depth now reflects perpendicular depth 2022-10-26 22:24:18 +11:00
Dion Moult fb2989c17f Fix #2528. Setting x angle on slabs now only affects the X euler angle 2022-10-26 18:11:54 +11:00
Dion Moult ffbf6483b5 You can now disconnect a wall from a roof clipping 2022-10-26 17:53:20 +11:00
Dion Moult 5c5488fa49 You can now clip walls by sloped slabs / roofs. 2022-10-25 23:13:44 +11:00
Dion Moult 7ff1b8a34d New API for connecting and disconnecting elements 2022-10-25 17:54:24 +11:00
Dion Moult fb786bb90c Batch reassignments now take advantage of inverse indices for a slight speedup. See #2408. 2022-10-25 16:27:13 +11:00
Dion Moult fad80eacaf Bump IOS 2022-10-25 14:03:25 +11:00
Gorgious56 84020c9832 Type Manager : Add search to ifc class and descriptions to class an predefined type enums 2022-10-24 15:20:52 +02:00
Dion Moult 59bce590cc Fix #2529. Bulk copying decomposed elements to multiple storeys now maintains decomposition. 2022-10-24 22:57:23 +11:00
Dion Moult a7ae78ec2a Fix crash when deleting opening. See #2529. 2022-10-24 21:49:44 +11:00
Dion Moult 523a97329e Fix door appearing on wrong RL when not on ground floor. See #2529. 2022-10-24 20:34:09 +11:00
Dion Moult 248dab936d Experimental reimplementation of reloading an updated IFC 2022-10-24 13:36:50 +11:00
Dion Moult a908fa62ba Loading model elements is now half aware of existing elements and will reuse where possible. 2022-10-24 13:07:45 +11:00
Dion Moult 2f7643424d IfcDiff now uses the iterator to compare geometry, which seems significantly faster. 2022-10-24 13:06:03 +11:00
Dion Moult 226c94381b IfcDiff now supports file objects as inputs instead of filepaths 2022-10-23 23:33:52 +11:00
Dion Moult 548a2972bc You can now add new profileset based types easily in the type manager. 2022-10-23 21:17:52 +11:00
Dion Moult 6263110733 You can now add layerset types in the type manager and access the manager on blank projects. 2022-10-23 20:02:34 +11:00
Dion Moult 89e8bf1ed6 You can now add new mesh based types in the type manager 2022-10-23 18:15:33 +11:00
Dion Moult 3809b5a34b You can now create new empty types, and select type objects from the type manager 2022-10-23 16:53:09 +11:00
Ryan Schultz 3cda25773d Maintaining the MaterialLayer thickness when X_Angle is used (#2525) 2022-10-23 15:35:17 +11:00
Ryan Schultz 77f2ad8f68 'X angle' rotates ifcslab/ifcramp in space. (#2522)
* 'X angle' rotates ifcslab/ifcramp in space.

* rotation w/ mathutils.Matrix.Rotation instead
per https://github.com/IfcOpenShell/IfcOpenShell/pull/2522#issuecomment-1287969832
2022-10-23 15:32:31 +11:00
Dion Moult 733b89be12 Link to C++ API reference from Python Sphinx docs 2022-10-23 12:15:11 +11:00
Thomas Krijnen 6346c5ba47 #1444 V3d_DirectionalLight compatibility fix 2022-10-22 20:30:13 +02:00
Thomas Krijnen dbb342d494 Small changes to code_examples.rst 2022-10-21 09:25:55 +02:00
Massimo Fabbro cb0ad67bcc Fixed errors and little improvement 2022-10-21 09:17:40 +02:00
Massimo Fabbro f0266a16a3 Little improve about code example doc and geometry setting doc 2022-10-21 09:17:40 +02:00
Thomas Krijnen cdde536651 Test cases for #2486 and #2517 2022-10-20 20:31:56 +02:00
Thomas Krijnen c1aca6ba8c #2517 Don't free parsed simple types as part of parent entity, but at file destruction 2022-10-20 20:30:48 +02:00
Thomas Krijnen f9391f80c4 #2408 test case 2022-10-20 13:36:00 +02:00
Thomas Krijnen f012286eda #2408 ifcopenshell.file.get_inverse(with_attribute_indices) 2022-10-20 13:35:02 +02:00
Dion Moult 45087e8822 The BIM Tool now provides similar slab-like editing tools for ramps and roofs 2022-10-20 20:51:32 +11:00
Dion Moult 8fa944d2a8 You can now have sloped slabs, good for doing roof slabs. 2022-10-20 19:56:15 +11:00
Dion Moult 67955b4e35 Batch reassign utility to address performance issues in #2408. 2022-10-20 18:25:14 +11:00
Dion Moult 9a0f29170a Fix #2518. Don't fail on non-existent schemas to allow for custom schemas 2022-10-20 15:03:33 +11:00
Dion Moult d09286eeca Schema migrator now supports IfcElectricDistributionPoint 2022-10-20 14:52:39 +11:00
Gorgious56 89f64eacef Remove info mode and streamline entity url fetching 2022-10-19 18:42:05 +02:00
Gorgious56 3334ea5c10 Fix error when toggling viewport annotations if there is no active camera in the current scene 2022-10-19 16:13:53 +02:00
Dion Moult 16b05d9981 Fix support for document references in IFC2X3 2022-10-19 20:17:10 +11:00
Thomas Krijnen 7f278fbde4 Import submodule explicitly 2022-10-19 10:44:29 +02:00
Dion Moult 2cad62554e Fix #2510. 2022-10-19 18:54:42 +11:00
Boris Brangeon 83bd0a973c Add function grouped_by in Selector (#2515) 2022-10-19 18:49:06 +11:00
Boris Brangeon b2c5153c1c Add IsGroupedBy in function get_decomposition (#2513)
* Add IsGroupedBy in function get_decomposition

selector example : "@ .IfcZone & .IfcSpace"

* Update element.py

* Change name function get_group

get_group to get_grouped_by
2022-10-19 18:48:55 +11:00
Dion Moult ec955ca717 You can now draw walls at a set RL 2022-10-19 17:54:25 +11:00
Dion Moult 9c28a95f41 Fix #2416. 2022-10-19 12:44:55 +11:00
Dion Moult 80020970e1 Purge old dynamic void system as Blender booleans are not sufficient. 2022-10-19 12:32:02 +11:00
Dion Moult f55b47dcb3 Include sample file generator in IfcTester doc generator 2022-10-19 11:54:30 +11:00
Dion Moult 7197db796c Fix #2508 2022-10-19 09:48:05 +11:00
Dion Moult a5aa970977 Add support for normalised ratio diffuse colours. See #2509. 2022-10-19 09:37:22 +11:00
Dion Moult 159e2ca779 Run black and minor fix. See #1990. 2022-10-19 09:17:37 +11:00
Gorgious56 ec1fbabd00 Dynamic Attribute Info (#1990)
* Add info mode toggle in addon prefs

* Add documentation property to Attribute

* Add webbrowser open operator

* Populate attribute doc on import

* Add attribute doc popup operator

* use path_resolve instead of eval

* Rename operator

* Add description field

* Panel UI is managed by operator

* Revert "Panel UI is managed by operator"

This reverts commit a7bbd06865.

* Revert "Revert "Panel UI is managed by operator""

This reverts commit d62115bf63.

* Add info popup to ifc class panel in object properties

* Implement fetching doc for non-ifc classes from jsonlike format

* Draw doc from non ifc classes

* Add utility function to draw info button on a given layout

* Add info buttons in the create project UI

* Display entity descriptions as tooltips in enum items

* Move predefined types fetching to data class

* Remove leftover prop code

* Use tool to get schema
2022-10-19 08:17:31 +11:00
Dion Moult b70bf5ed33 Cute developer function to spit out a debug object to help when coding. 2022-10-18 23:11:37 +11:00
Dion Moult d6be887bde Minor IfcTester test case fixes 2022-10-18 23:11:11 +11:00
Dion Moult 6098d94223 Fix #2507. 2022-10-18 22:47:42 +11:00
Dion Moult f045e86a38 Rebuild wall engine (again) to allow for sloped wall clippings. 2022-10-18 20:02:00 +11:00
Dion Moult 9ef65c9cc5 Add basic support for sloped walls, sloping along the local X axis 2022-10-17 22:52:44 +11:00
Ryan Schultz f8a8250429 searching for an IfcMaterial in IfcMaterialDefinitions (#2503)
* searching for an IfcMaterial in IfcMaterialDefinitions
A start to trying to address: https://community.osarch.org/discussion/comment/13024/#Comment_13024

* refactored to a tighter version. Thanks Thomas.
2022-10-17 20:24:05 +11:00
Dion Moult a75341b36e Fix #2505. Missing rotate button in UI. 2022-10-17 20:11:37 +11:00
Dion Moult 0a0271a30e Filled openings can now be based off a custom profile representation 2022-10-17 18:34:34 +11:00
Andrej730 871f6ebdf1 Updated windows batch script for setting up environment for blenderbim
Previous batch scripts was resulting in broken sym links (probably because mklink and some other comands were expecting full paths). New script provide required full paths and also use 1 line to set up blenderbim location.
2022-10-17 12:19:53 +06:00
Dion Moult 90f1a179bc You can now flip the direction of doors and windows 2022-10-16 23:50:00 +11:00
Andrej730 81d80ee415 Doc API is now accessible without setting up DocAPI class
Now you can access Doc API functions directly (like `ifcopenshell.util.doc.get_entity_doc(...)`) without setting up DocAPI class.
To make it work I've set up singleton DocDatabase class and made all paths relative to module location.
2022-10-16 16:54:05 +06:00
Dion Moult 1eeb820d26 Flipping walls with fillings now works. See #2491. 2022-10-16 21:05:05 +11:00
Andrej730 4dc860c019 Added IFC v4.0 json schema and added docs API for working with schemas
The schema for IFC4 the same as it was for IFC2x3.
Properties schema for IFC4 includes both quantity and propety sets.

Also cleaned up entities description for IFC2x3 from HTML code and IFC tags.
2022-10-16 13:32:22 +06:00
Dion Moult cb28a0b298 Flipping walls with openings now works. See #2491. 2022-10-16 17:58:06 +11:00
Dion Moult 76e6923a3e Filled openings now handle offset wall axes 2022-10-16 10:16:10 +11:00
Dion Moult 56a09e51bd Modeling dimensions now affect newly created walls and profiles 2022-10-15 22:53:19 +11:00
Dion Moult b90ab70658 Modeling dimensions now have sensible defaults depending on units and show prior to element creation 2022-10-15 11:06:21 +11:00
Andrej730 28aab490df IFC v2.3 json schema and parsing tool to regenerate it (#2501)
* IFC v2.3 json schema and parsing tool to regenerate it

Added entities and propeties schema in json format.

ifc2x3_entities.json format:
entity_name -> description, url, attributes;
attributes format: attribute_name -> attribute_descrption

ifc2x3_properties.json format:
property_set_name -> property_name -> description, children
children format: child_property_name -> child_property_description.

Also added doc.py script that can generate the same json schema files but requires docs IFC 2.3 docs in the same folder (https://github.com/buildingSMART/IFC/tree/Ifc2.3.0.1)

* Moved all json schema files to /util/schema

* Moved json files back to /util/, changed doc.py export location

* Added specification urls for entities and properties

Added specification urls for entities and properties.
Also added a function to parse actual property sets domain from the website to generate specification urls (domain on website and on github do not match).

Schema structure was changed:

ifc2x3_entities.json format:
entity_name -> description, spec_url, attributes;
attributes format: attribute_name -> attribute_descrption

ifc2x3_properties.json format:
property_set_name -> properties, spec_url
properties format: property_name -> description, children_properties
children_properties format: child_property_name -> child_property_description.

ifc2x3_property_sets_domains.json format:
property_set_name -> ifc_domain
2022-10-15 07:14:34 +11:00
Thomas Krijnen 0810fa7564 Escape \W in selector grammar 2022-10-14 11:30:11 +02:00
Dion Moult a34c0cb35c Fix a variety of IFC validation errors in entity creation and asset appending 2022-10-14 18:19:35 +11:00
fbpyr 10bde73c1a choco nightly path (#2498)
* fix choco nightly scripts path

* fix choco nightly scripts path
2022-10-14 17:16:50 +11:00
Massimo Fabbro 670399f327 Add gross volume calculation (#2487)
* Now gross volume calculation doesn't consider related voids

* Qto gross volume calculation minor fixes

* No need to create a new entity and better mesh creation

* Use bmesh to calculate volume so it's not necessary to create a new object
2022-10-14 16:50:04 +11:00
Dion Moult 241c5b1f48 Fix bug where unnamed types didn't show up in the UI. 2022-10-14 10:08:52 +11:00
Dion Moult 9ef3b4389b Change pip name to ifcopenshell. See #2468. 2022-10-14 08:25:15 +11:00
Dion Moult 2d37d3c914 More robust type switching of parametric objects 2022-10-13 22:51:06 +11:00
Thomas Krijnen ede9c5fca5 Fixes to svgfill/cgal/mpfr/gmp for -shared build on nix 2022-10-13 09:52:22 +02:00
Thomas Krijnen 4cccfd08d3 #671 check for zero magnitude vector in newells method 2022-10-13 09:34:14 +02:00
Thomas Krijnen 153c5a06e7 #671 use new faceset_helper api in IfcFace 2022-10-13 09:22:49 +02:00
Thomas Krijnen 9a4d3e706a #671 api in faceset_helper to obtain multiple bounds from single loop in case of intersections 2022-10-13 09:22:28 +02:00
Thomas Krijnen 0b20d3ae2c #671 correct check for NO_WIRE_INTERSECTION_CHECK setting 2022-10-13 09:21:50 +02:00
Thomas Krijnen 92fb46558f #671 perform fix on wires after wire_intersections() 2022-10-13 09:20:44 +02:00
Dion Moult 8be1a913ec Basic support for mesh booleans 2022-10-13 17:04:53 +11:00
Dion Moult c354de7b83 Minor fix 2022-10-13 11:50:33 +11:00
Dion Moult 19b718cdf5 Thumbnails auto load when projects are loaded. 2022-10-13 10:19:09 +11:00
Dion Moult cdffeba4cc Map units are now supported in converting to and from local to global coordinates 2022-10-12 20:32:16 +11:00
fbpyr e6e8db8a00 choco yesterday (#2495)
* fix check for available bbim releases yesterday

* fix check yesterday bbim releases after midnight

* fix check all releases url
2022-10-12 17:39:50 +11:00
Thomas Krijnen 8dab1810c7 #2486 #1861 Don't fail on overwriting header values 2022-10-11 10:48:53 +02:00
Dion Moult c5c9a192b0 You can now duplicate existing types in the type manager 2022-10-11 17:51:01 +11:00
Dion Moult 4279576d3c Type manager can now browse to type classes that don't have any types yet 2022-10-11 17:34:17 +11:00
Dion Moult dff35c173d You can now delete a type when managing types 2022-10-11 15:31:31 +11:00
Dion Moult 38f563d4f4 New timerless thumbnailed type manager. 2022-10-11 13:49:46 +11:00
Dion Moult 36fdd3f7fc Bundle pillow as dependency for thumbnail generator 2022-10-11 10:54:08 +11:00
fbpyr 47e0ec43c2 fix get sha256sum directly from release download (#2493) 2022-10-11 09:40:00 +11:00
Thomas Krijnen 0446fca56a --json --spf option in validate.py 2022-10-10 14:25:45 +02:00
Thomas Krijnen 785dc2a300 Test case for string formatting 2022-10-10 14:20:21 +02:00
Thomas Krijnen 64ff046e96 #2482 Use u32string for character encoder, remove CODECVT_BOOST_FALLBACK 2022-10-10 14:19:42 +02:00
Dion Moult 16582cf57f Experimental new thumbnail with smarter layer / profile thumbnails and no timers. 2022-10-10 23:13:37 +11:00
Dion Moult a5517071cc Make remove opening more accessible in UI and refactor 2022-10-10 11:45:50 +11:00
Gorgious 5d5ab3dafd Fix bad indenting in last commit
Sorry, I'm a bit rusty !
2022-10-09 22:05:56 +02:00
Gorgious ccfbeab51e Creating a wall with a IfcMaterialLayerSet with the BIM tool now makes it active (yellow outline)
+ deduplicate code under new function
2022-10-09 22:04:01 +02:00
Dion Moult c9214bf04a The BIM Tool workspace now intelligently reflects the active object 2022-10-09 23:51:40 +11:00
Dion Moult 492a535312 You can now set a fixed RL for inserted windows 2022-10-09 23:29:06 +11:00
Dion Moult 278931f5db Minor fix 2022-10-09 22:40:20 +11:00
Dion Moult d847f358ba Minor fix 2022-10-09 22:26:38 +11:00
Dion Moult ab31fd7e48 Copying filled openings is now possible. 2022-10-09 22:25:55 +11:00
Dion Moult 32f0911479 Elements with filled openings don't have their openings copied, as it depends on the fill 2022-10-09 22:22:12 +11:00
Dion Moult bdbd874fa8 Fix #2488. Copying objects now copy deep instead of adding afresh (and losing parametric data and openings). 2022-10-09 18:09:22 +11:00
Dion Moult 232252f61d Copy deep can now exclude things, useful for copying representation graphs where you don't want to duplicate the context 2022-10-09 17:15:39 +11:00
Dion Moult 3ee4b1f0e3 Fix #2489. Prevent openings from being added twice and only save changed openings. 2022-10-09 15:22:10 +11:00
Thomas Krijnen cd27d48701 #2482 entity_instance.to_string() 2022-10-08 17:18:47 +02:00
Thomas Krijnen 94ba1a121d #2486 store provenance to file also in simple type instances 2022-10-08 15:16:53 +02:00
Dion Moult 4110b81270 Decomposition mode now selects all children (not parenting) to move a host and all children easily 2022-10-08 23:42:03 +11:00
Dion Moult 37028857b4 Suspicious undo stack fix 2022-10-08 22:29:13 +11:00
Dion Moult 7c1f02cafe You can now reinsert windows and doors into other hosts 2022-10-08 22:28:46 +11:00
Dion Moult 7d779130e1 Removing products now handles purging a bunch more inverse relationships 2022-10-08 21:15:01 +11:00
fbpyr fb6d24b3ed add choco alpha version tag, description fixes (#2484) 2022-10-07 22:14:04 +11:00
Dion Moult c6ee1d1679 Deleting a fill auto deletes the opening 2022-10-07 20:55:46 +11:00
Dion Moult 1db5d3b06c Begin purging old dynamic void feature 2022-10-07 20:47:13 +11:00
garylzimmer 3b81c158f3 Added auto renaming for null project name (#2478)
Sorry it took me almost a year to implement @aothms excellent suggestion!
https://github.com/IfcOpenShell/IfcOpenShell/issues/1954#issuecomment-1002463624
2022-10-07 20:36:25 +11:00
Dion Moult 34aff01eb2 New prototype operator to regen fill openings 2022-10-07 17:06:19 +11:00
Dion Moult 7d1a41c1c7 Minor fix 2022-10-07 16:08:21 +11:00
Dion Moult d187c588d3 Minor fix 2022-10-07 16:04:06 +11:00
Dion Moult 82f847f5d4 Basic adding of filled openings reimplemented with new opening system 2022-10-07 16:00:17 +11:00
Dion Moult 27dc81544c Minor fixes to IfcTester testcases 2022-10-07 13:34:19 +11:00
Dion Moult 1677a6c83f IfcTester now supports new entityType data in partof facet 2022-10-07 13:11:52 +11:00
Dion Moult 61f4c48a4a Write docs for basic description of what's in an IFC 2022-10-06 21:07:58 +11:00
Dion Moult 0259b75d4c Write documentation for IOS code examples 2022-10-06 19:56:53 +11:00
Dion Moult 0db28917a4 Write docs on IOS geometry processing. See #671. 2022-10-06 17:06:37 +11:00
Dion Moult 15b4ab543b Update geom settings with newly added settings, and add example code. See #671. 2022-10-06 15:52:12 +11:00
Dion Moult 72147e51f5 Migrate old Doxygen geom markdown to new Sphinx rst docs. See #671 2022-10-06 11:18:23 +11:00
Sigma Dimensions 835e3f00b4 fix the bug fix on commit #88893a8 2022-10-05 13:59:41 +01:00
Thomas Krijnen 2dee2afdfa #1861 test case 2022-10-05 13:07:16 +02:00
Thomas Krijnen 3d82791a99 #1861 update globalid map on attribute change 2022-10-05 13:03:52 +02:00
Dion Moult 2cb8eebf83 Profiles now retain manual booleans alongside autogenerated clipping planes 2022-10-05 21:52:07 +11:00
Dion Moult 49d3ee2f73 Copying an opening now tracks it as a special objects in the scene 2022-10-05 20:04:19 +11:00
Dion Moult aeaef641bf Bump IOS build 2022-10-05 19:52:24 +11:00
Dion Moult e97d427e9f Copying elements with openings now also copies openings 2022-10-05 19:44:32 +11:00
Dion Moult d525dfa46f Deleting voided objects now works with new opening system 2022-10-05 18:22:35 +11:00
Dion Moult 272a00941e Openings now move with profiles, but stay fixed along the local Z axis 2022-10-05 17:57:51 +11:00
Dion Moult 1590e8c0e7 Openings locations are fixed relative to slab profile editing 2022-10-05 16:39:10 +11:00
Dion Moult ac4e2f142b Openings now move with the host, but also intuitively stay along the local X axis of the host 2022-10-05 15:26:27 +11:00
Dion Moult 428ee648b4 Wall representations now distinguish between auto and manual booleans and manual booleans move with the wall 2022-10-05 13:09:18 +11:00
Thomas Krijnen fa6bbf2d4a A bunch of not so pretty validate.py performance improvements 2022-10-04 15:16:34 +02:00
Dion Moult 12aa66fdcb Fix nasty bug related to inconsistent behaviour of wall intersection math 2022-10-05 00:14:12 +11:00
Thomas Krijnen f68db3d3be Fixes after 698c708bf6 2022-10-04 12:38:40 +02:00
Dion Moult 1f56b36bfa Minor fix 2022-10-04 20:16:36 +11:00
Thomas Krijnen be805a3e96 Fixes after 698c708bf6 2022-10-04 11:14:13 +02:00
Thomas Krijnen 5d95475970 #2471 test case 2022-10-04 10:23:27 +02:00
Thomas Krijnen f41ad0d5de #2471 work around file gc with instance refs 2022-10-04 10:23:08 +02:00
Dion Moult f81b22240c Minor fix 2022-10-04 18:35:01 +11:00
Dion Moult 7d63b03dd3 Fix bug where linking an element twice caused stacked undos to fail. See #2200. 2022-10-04 14:23:02 +11:00
Dion Moult e74eb0cc53 Fix bug where unlinking operations were not stored in the undo stack 2022-10-04 13:41:27 +11:00
Dion Moult 008d6bed5c PIP publish now only happens once a month, on a full moon 2022-10-04 10:41:25 +11:00
Dion Moult d0985a23d0 Fix critical bug where default context was not set on project load 2022-10-04 10:03:59 +11:00
Massimo Fabbro a5d49e81a0 Fix little bug in qto_calculator about net footprint area (#2470) 2022-10-04 09:59:50 +11:00
Dion Moult bfcc086aad Attempt to change PyPI name to ifcopenshell 2022-10-03 23:40:29 +11:00
Dion Moult 0eae3de6a2 Openings now move with the host by default. Small change, big impact. 2022-10-03 23:30:19 +11:00
Thomas Krijnen 1d39413717 Test cases for previous commits 2022-10-03 14:29:41 +02:00
Thomas Krijnen c57e5e2100 Capture lower level errors in validate.py reported in C++ 2022-10-03 14:19:29 +02:00
Thomas Krijnen ca5249a179 More resiliance against wrong attribute counts 2022-10-03 14:05:08 +02:00
Thomas Krijnen 698c708bf6 Reduce heap allocations during parse by reusing vector for top level instances 2022-10-03 14:04:42 +02:00
Thomas Krijnen 084151ed4c Augment invalid entity message with offset in file 2022-10-03 14:03:53 +02:00
Thomas Krijnen c56c7288e3 Clarify wrong attribute count message 2022-10-03 14:03:04 +02:00
Thomas Krijnen f5398c77d5 Clarify invalid entity message 2022-10-03 14:02:28 +02:00
Thomas Krijnen a724b5cb24 Clarify character offset in exception 2022-10-03 14:02:04 +02:00
Thomas Krijnen f3a15953b2 Missing include 2022-10-03 14:01:43 +02:00
Dion Moult 567548c7f3 Fix bug where adding an opening didn't create a relative placement 2022-10-03 22:35:53 +11:00
Dion Moult 8c25349b49 Add new opening functions to BIM tool workspace UI 2022-10-03 20:50:33 +11:00
Amoki 49cd966d43 remove-yacc 2022-10-03 11:16:42 +02:00
Dion Moult b552ea1376 Openings now can be explicitly edited and saved and use IOS instead of Blender modifiers 2022-10-03 01:02:08 +11:00
Dion Moult 9c91ee31e5 New booleans UI panel to manage representation boolean stack 2022-10-02 23:48:19 +11:00
Carlos Villagrasa e9a063ccbd Construction Types Browser - Multipreview now runs smoothly (#2463)
* Construction types multipreview now runs smoothly

* Minor fix

* Minor fix
2022-10-02 16:46:14 +11:00
Dion Moult e4e9db0b3e Implement show and hide openings and hide booleans operator 2022-10-01 23:45:49 +10:00
Dion Moult 2ec41bf3a0 Add support for processing rectangular profiles in the profile editor 2022-10-01 23:27:05 +10:00
Dion Moult 944cbeea1a Handle both 2D and 3D placement matrixes in IfcOpenShell utils 2022-10-01 23:25:34 +10:00
Dion Moult 4b8238a602 Openings are no longer loaded when loading fresh IFC projects 2022-10-01 20:32:40 +10:00
Dion Moult 8789a19f57 Basic show and remove representation boolean operators 2022-10-01 20:31:46 +10:00
Kristoffer Andersen 8c8e83d98f update ci-daily build use win2019 for upload 2022-10-01 10:04:24 +02:00
Dion Moult 527d26c1c6 Openings are now specially shaded, and you can now add half space clipping booleans
# Conflicts:
#	src/blenderbim/blenderbim/bim/module/model/ui.py
2022-10-01 14:33:33 +10:00
Massimo Fabbro e2a5b09d77 Add calculate all qtos button: initial fixing (#2456) 2022-10-01 10:55:42 +10:00
Kristoffer Andersen 98b6edf119 Merge pull request #2461 from Krande/fix-daily-conda-build
fix: daily builds of conda ifcopenshell
2022-09-30 19:42:34 +02:00
Carlos Villagrasa 0a05197d3e Construction Types Browser: Solution to most of the current issues (#2460)
* Delay asset generation

* bpy.app.is_job_running only in Blender >= 3.3

* First time preview on sidebar works again

* Avoid flickering

* Show multiple previews on the first run

* Preserve mouse position with hacky operator
2022-10-01 02:01:26 +10:00
krande 75645a837a fix: daily builds of conda
Ran into a lot of `build not compiled for windows` errors on windows. So using the host and dependencies from conda-forge meta.yaml and pin boost-cpp to <=1.74 to at least allow windows builds of ifcopenshell to be created while we figure out how to support boost-cpp >=1.78
2022-09-30 17:49:55 +02:00
Thomas Krijnen 40e463a55f #2457 uplift curves to wires in geometric sets 2022-09-30 13:59:31 +02:00
Dion Moult 7b817f7359 Minor fix 2022-09-30 18:53:14 +10:00
Dion Moult 22e69a606d CAD sketcher constraints can now be saved and loaded from IFC 2022-09-30 16:04:16 +10:00
Dion Moult 8c7b2ab2a1 Slab depth auto-updates on type/layer changes with new slab system 2022-09-30 13:43:23 +10:00
Dion Moult 3512a2ce36 Add undo support to profile and wall element operators 2022-09-30 13:08:39 +10:00
Dion Moult 9e76b2b9eb Footprints are now supported in the new profile editor 2022-09-30 13:00:40 +10:00
Dion Moult a08a78b3c9 Slabs are now first created directly in IFC instead of reverse engineering meshes 2022-09-30 12:08:57 +10:00
Dion Moult 9fc5f72831 Minor fix 2022-09-29 23:51:25 +10:00
Dion Moult 391e09c16c Convenience operator to draw rectangles when editing profiles 2022-09-29 23:31:11 +10:00
Dion Moult 60e4de5ffa You can now align the view to the edited profile, for angled profiles 2022-09-29 22:33:32 +10:00
Vukas Pajic a248f16cf8 indentation 2022-09-29 14:30:25 +02:00
Vukas Pajic 9240a10fa8 no need for Shapely.libs when using macos 2022-09-29 14:25:42 +02:00
Vukas Pajic 19d3208744 add Shapely.libs 2022-09-29 14:08:24 +02:00
Vukas Pajic c080567f53 update makefile for shapely dependancy (#2452)
* initial

* unzip instead of tar

Co-authored-by: Dion Moult <dion@thinkmoult.com>
2022-09-29 22:01:54 +10:00
Dion Moult ac72f41051 You can now change the radius of existing arcs. Minor interface cleanup. 2022-09-29 17:24:00 +10:00
Dion Moult fc9273c1d3 You can now change the radius of existing circles. Radius settings are remembered. 2022-09-29 16:47:40 +10:00
Dion Moult 43dc2da05f Minor fix 2022-09-29 15:43:19 +10:00
Dion Moult 121190c9b9 Vertex group indices in repeated operations do crazy stuff. This is more robust. 2022-09-29 15:41:06 +10:00
Dion Moult 0f2f6e2721 More robust arcindex generation and let user specify radius for circles 2022-09-29 11:41:52 +10:00
Carlos Villagrasa 05070c3758 Construction Types - Attempt to solve repeated object creation (#2450)
* Constr types dicts to Blender props

* Reduce update interval

* Base for not linking dummy asset objects to collections
2022-09-29 10:00:39 +10:00
Dion Moult d4d923e51b New feature to fillet profiles with true arcs 2022-09-28 23:35:29 +10:00
Dion Moult 766ffa71cd Operator to clear parametric vertices when editing profile 2022-09-28 23:04:07 +10:00
Dion Moult cbf213952b Minor fix 2022-09-28 22:45:03 +10:00
Vukas Pajic b7c76048ab comment out tests 2022-09-28 14:24:17 +02:00
Kristoffer Andersen 20fdb09a0c Update meta.yaml
Do not force boost version 1.80.0. It fails horribly
2022-09-28 14:21:47 +02:00
Vukas Pajic 1943d59339 Add additional quantity calculations to QtoCalculator (#2405) 2022-09-28 14:20:10 +02:00
Dion Moult d2836e07d5 Minor fix 2022-09-28 22:18:54 +10:00
Dion Moult 90a788082d New 2D CAD offset operator 2022-09-28 21:07:28 +10:00
Kristoffer Andersen eafad2862f Update meta.yaml
test using boost 1.80.0
2022-09-28 09:21:39 +02:00
Dion Moult 1c0fa13f39 Fix CAD arc bug where sometimes arc went the wrong way, and now auto-clean verts when creating arc 2022-09-28 11:02:49 +10:00
Carlos Villagrasa 2e5ce6b862 Fix bim tool crash (#2446)
* Attempt to wait for assets (triggers infinite loop)

* Using timer instead of sleep

* remove sleep import

* Reduce asset update interval

Co-authored-by: Dion Moult <dion@thinkmoult.com>
2022-09-28 09:30:52 +10:00
Ryan Schultz f63a33647b relocate remove drawing button per https://community.osarch.org/discussion/comment/12744/#Comment_12744 (#2444)
* added 3"=1'-0" scale (a common scale), and corrected 6"=1'-0" scale

* relocate remove drawing button per
https://community.osarch.org/discussion/comment/12744/#Comment_12744
2022-09-28 01:54:11 +10:00
Carlos Villagrasa 2f84c188f5 Fix bim tool crash (#2445)
* Attempt to wait for assets (triggers infinite loop)

* Using timer instead of sleep

* remove sleep import
2022-09-28 01:31:59 +10:00
Dion Moult 01b1b57f71 Mitre joints now auto merge and are accessible in the new profile editor 2022-09-27 23:35:40 +10:00
Dion Moult 1ad0ef82d4 Fix bug where CAD filleting sometimes led to crashes 2022-09-27 23:34:08 +10:00
Dion Moult 23212003e9 Add support for circles in profile editing 2022-09-27 17:16:26 +10:00
Dion Moult aff281d00b Add unit support in new profile editing tool 2022-09-26 22:40:33 +10:00
Dion Moult 4bbcf3fa1e Set and unset 3-point arcs only for profiles 2022-09-26 22:06:11 +10:00
Dion Moult 5e50a15b41 New smart profile editor for extruded elements inspired by CAD Sketcher 2022-09-26 18:00:05 +10:00
Massimo Fabbro b673fc94a5 Add calculate all qtos button (#2441)
* Add button to calculate all object qtos in qto n-panel

* Calculate all qtos button: now the schema is readed from the file

* Calculate all qtos button: now edit_qto runs only one time every object

* Add button to assign pset qto to selected objects

* Calculate all qtos button: small fixing and unable to execute if no selected objects

* Calculate all qtos button: first tool test

* Calculate all qtos button: use better built-in function to get ifc obj entity
2022-09-26 16:05:08 +10:00
Dion Moult 4c7a5e8a1a Experimental CAD Sketcher integration for simple arbitary polyline extrusions 2022-09-25 12:27:10 +10:00
Dion Moult 33ebcd7664 New convenient modifier to rotate beam/wall/column etc by 90 degrees 2022-09-24 21:46:32 +10:00
Dion Moult 76ce726f1d Implement refresh profiles on type and profile changes 2022-09-24 20:19:01 +10:00
Ryan Schultz 54da579891 Ifc text tweaks (#2431)
* added 3"=1'-0" scale (a common scale), and corrected 6"=1'-0" scale

* Allow float point values in smart tags, and avoid breaking when no assigned product See #2340, #2349
2022-09-24 20:07:32 +10:00
Dion Moult e024e9e622 You can now set cardinal point on creation and bulk change for profile elements 2022-09-24 16:21:20 +10:00
Dion Moult 56f4b72161 Reimplement profile cardinal points 1-9 with new profile engine 2022-09-24 15:42:50 +10:00
Dion Moult 7545313dee Removing an element now also purges connection relationships 2022-09-24 14:12:45 +10:00
Dion Moult 48eb6b70e3 You can now manually set the length/height of profiled extrusions 2022-09-23 23:53:12 +10:00
Dion Moult 5f2acb43b1 You can now recalculate connections manually for profiles 2022-09-23 23:41:37 +10:00
Dion Moult b7eaefee50 Fix bug when deleting elements may possibly create an invalid containment relationship 2022-09-23 23:26:29 +10:00
Dion Moult 13a387cc73 Add support for descriptions in path connection relationships 2022-09-23 23:00:27 +10:00
Dion Moult f139f2ba1e Fix bug where removing an element in an aggregation could leave the file in an invalid state 2022-09-23 23:00:07 +10:00
Dion Moult 1d8085f6c9 New profile engine now supports mitred profile joints. 2022-09-23 19:23:22 +10:00
Dion Moult 6699bfeb54 Mitred wall joints rebuilt on the new wall engine 2022-09-23 15:42:05 +10:00
Dion Moult 7cee0a85c6 3D axis representation contexts are now created by default for all new projects 2022-09-23 12:39:23 +10:00
Dion Moult 351dcfa241 Implement profile unjoin and start cleaning up legacy architectural tools panel 2022-09-23 12:38:49 +10:00
Dion Moult 28022581a7 You can now have connection aware L joints for profiled elements 2022-09-23 00:09:56 +10:00
Dion Moult 748aaa671d Extending profiled elements (e.g. beams and columns) now supports axis connectivity and uses new profile engine 2022-09-22 23:32:22 +10:00
Thomas Krijnen 95b474c37d Delete HdfSerializer.h~ 2022-09-22 13:13:57 +02:00
Thomas Krijnen 9bb9f91f57 Delete HdfSerializer.cpp~ 2022-09-22 13:13:47 +02:00
Dion Moult 7db967583b Profile extensions now use the new engine and support 3D axis representations 2022-09-22 16:05:35 +10:00
Dion Moult 5d43f6db37 Adding a profile now uses the new profile engine 2022-09-21 23:28:03 +10:00
Dion Moult 4cfa327159 New API functions for adding arbitrary profile and profile-based representations 2022-09-21 23:27:24 +10:00
Dion Moult 920177d8c6 Minor fix. See #2416. 2022-09-21 10:50:02 +10:00
Dion Moult 5f902600e3 Minor fix. See #2416 2022-09-21 00:04:02 +10:00
Dion Moult db9af13843 Fix #2427. Don't treat missing mandatory attributes as fatal in IFC version migration 2022-09-20 22:46:15 +10:00
Thomas Krijnen dd49c092c6 #2419 remove map erasure from destructor
de initialization order can result in map already destroyed when unique_ptr schemas are still being deleted
2022-09-20 11:35:08 +02:00
Thomas Krijnen d347a7f893 Don't run choco from other repos 2022-09-20 11:33:07 +02:00
Dion Moult 3c61804e8b Flipping walls now preserves connections, and flips around the reference axis 2022-09-20 17:21:11 +10:00
Dion Moult e179070978 New interface to show, select, and remove path connections 2022-09-20 16:25:52 +10:00
Dion Moult de2ada1141 Walls recalculate on type and layer thickness changes 2022-09-20 14:05:04 +10:00
Dion Moult eb48a68dd1 Copying elements do not copy connections to prevent mutually exclusive connectivity 2022-09-20 12:01:40 +10:00
Dion Moult fc6bf5e2bb You can now set an exact wall length 2022-09-20 10:48:44 +10:00
Dion Moult 3f2d86255d Merge projects now merges all entities, not just rooted ones. 2022-09-19 23:44:10 +10:00
Dion Moult d751ee9995 Fix critical typo in rendering styles creation that led to invisible styles 2022-09-19 23:10:56 +10:00
Dion Moult 83f70624a4 Attempt to get LXML working across all platforms. See #2422. 2022-09-19 22:23:09 +10:00
Dion Moult 356253c4f0 Minor fix 2022-09-19 21:40:53 +10:00
Dion Moult a459c39c97 Minor fix 2022-09-19 17:36:20 +10:00
Dion Moult e5719bd3c6 You can now change wall heights in bulk 2022-09-19 16:29:43 +10:00
Dion Moult 18387e7732 You can now switch contexts of of objects in bulk 2022-09-19 15:59:37 +10:00
Dion Moult 57e8e47061 Minor regression fix 2022-09-19 13:33:40 +10:00
Dion Moult 5c5bb03652 IfcPatch now allows passing in IFC objects instead of filepath strings 2022-09-19 13:31:33 +10:00
Dion Moult ea1d797605 Add documentation for IfcPatch 2022-09-19 12:25:35 +10:00
Dion Moult bc3bf07a60 Minor fix to 12DToRevitTINS patch 2022-09-19 10:49:03 +10:00
Dion Moult fbd4177d08 New feature to merge aligned walls and retain parametric connections 2022-09-19 09:12:42 +10:00
Thomas Krijnen 4842891fd7 #2322 Implement IfcSweptDiskSolidPolygonal.FilletRadius 2022-09-18 15:59:24 +02:00
Dion Moult f6056f9bc3 Shift-Y in the BIM tool for walls now regenerates wall connections 2022-09-18 23:26:18 +10:00
Dion Moult c7af6c4482 Fix #2420. Bug where project library assets sharing relationships with other assets would pull in other assets 2022-09-18 23:19:23 +10:00
Dion Moult c9c905fd3e Splitting walls now uses the new wall engine 2022-09-18 22:00:27 +10:00
Dion Moult 7f76488869 New recalculate wall operator to manually trigger an update of all wall connections 2022-09-18 15:57:07 +10:00
Dion Moult cd930d809a New wall engine slowly being wired up to wall join hotkeys 2022-09-18 15:56:23 +10:00
Dion Moult f49d7ec2cb Implement unjoin in new wall engine 2022-09-18 15:54:24 +10:00
Dion Moult 322abdff53 Minor fix 2022-09-17 22:19:49 +10:00
Dion Moult 984978bc00 Add axis support to new wall engine 2022-09-17 21:52:03 +10:00
Dion Moult ce8da8bd85 An axis context is now added by default for all new projects 2022-09-17 21:41:09 +10:00
Dion Moult 6d0fdb32c6 Fix #2416. 2022-09-17 14:51:50 +10:00
Dion Moult 0cb3c0a907 Update docs about new Apple M1 support 2022-09-16 22:29:04 +10:00
Dion Moult d9ea62a670 Distribute Apple M1 to PyPi 2022-09-16 22:21:58 +10:00
Dion Moult 2228104a8e Apple M1 builds. This stuff never works the first time. 2022-09-16 21:48:38 +10:00
Dion Moult e473574086 Setup BlenderBIM Apple M1 builds. Fingers crossed! 2022-09-16 20:30:33 +10:00
Dion Moult f93604d6b7 Implement connection aware wall L-joints 2022-09-16 18:30:57 +10:00
Dion Moult d106b0e017 Fix #2413. Bug where multiple tasks may accidentally override animation keyframes. 2022-09-16 11:53:25 +10:00
Dion Moult 90e35810d9 Prototype connection-aware wall extension and T-joints 2022-09-15 22:44:06 +10:00
Bruno Postle 4fb910fc44 wrapper is a bash script not a sh script 2022-09-15 07:42:00 +01:00
Dion Moult 280ce56431 Creating walls are more stable and now directly goes to the IFC representation instead of reverse engineering Blender meshes 2022-09-15 10:46:52 +10:00
Dion Moult 81e46b533b New API to directly create wall-like representations without Blender geometry 2022-09-15 10:44:43 +10:00
Dion Moult 171db4b241 Add support for connecting/disconnecting paths in API 2022-09-15 10:32:01 +10:00
Martin15135215 8fe9ddd599 Small Change
https://github.com/IfcOpenShell/IfcOpenShell/issues/2409#issuecomment-1246767831
2022-09-15 00:13:01 +10:00
Dirk Olbrich b299694ca0 doc: library flags for macos build 2022-09-14 15:19:53 +02:00
Dirk Olbrich ca76b77130 update doc for building on macOS 2022-09-14 15:19:53 +02:00
Thomas Krijnen e94a5c97df Merge remote-tracking branch 'remotes/origin/pr/1911' into v0.7.0 2022-09-14 14:57:27 +02:00
GisSpace df845653f3 fix IFC to xml - IFCGROUP in IFC cannot be found in xml #1154 2022-09-14 14:54:05 +02:00
Thomas Krijnen a3e499656c Don't log vertex counts in helper when they didn't change 2022-09-14 14:13:24 +02:00
Thomas Krijnen 79bb40092f Apparently --disable-static no longer works? 2022-09-14 10:57:08 +02:00
Thomas Krijnen 2c7a4ca880 Filter unsupported py versions on recent osx 2022-09-14 10:56:52 +02:00
Thomas Krijnen 6a4375cf54 Update build-all.py versions 2022-09-14 10:56:16 +02:00
Thomas Krijnen c74b79213b Remove asterisks before branch name 2022-09-14 10:55:32 +02:00
Ryan Schultz 806d147a85 A preference for locking grids upon import 2022-09-14 18:33:26 +10:00
Dion Moult 227b1391bd Clarify behaviour of funny characters and pattern matching on numbers 2022-09-14 10:15:19 +10:00
Bruno Postle 68e2bb1fa7 oops, correct version of wrapper that quotes special characters 2022-09-13 23:21:49 +10:00
Dion Moult fccc233f42 Merge pull request #2406 from brunopostle/desktop-files2
Configure blenderbim file associations and desktop icon
2022-09-13 23:06:04 +10:00
Bruno Postle 86785ab181 Configure blenderbim file associations and desktop icon 2022-09-13 13:57:13 +01:00
Dion Moult 3255053ac4 Implement all restrictions for IfcTester 2022-09-13 22:09:40 +10:00
Dion Moult 422cc4802c Implement optionality in IfcTester, and generate integration testcases 2022-09-13 19:50:51 +10:00
Dion Moult fc0b0ca188 Minor fix to ids.xsd 2022-09-13 19:44:36 +10:00
Dion Moult 3120b0c6e2 Add support for IfcTester and predefined properties 2022-09-12 23:03:50 +10:00
Dion Moult 91fac6d4bd Add IfcTester support and test cases for bounded, table, and reference props 2022-09-12 22:35:05 +10:00
Dion Moult c7c59d9496 IfcTester now supports list and enumerated properties 2022-09-12 21:34:06 +10:00
Dion Moult 73ac5d314b Complex properties and quantities are not supported in IDS 2022-09-12 20:10:25 +10:00
Dion Moult 3b2cbac9d1 Generate stable GlobalIds for IDS docs to prevent commit noise 2022-09-12 20:10:25 +10:00
Thomas Krijnen a63d6475b2 Keep moving schema agnostic code out of kernel 2022-09-12 10:34:11 +02:00
Thomas Krijnen 4cd0932d7d Fix to boolean utils, version detection, and behaviour on old versions 2022-09-12 10:34:11 +02:00
Dion Moult cb447fe902 IfcTester Add support for qto checks 2022-09-12 18:26:11 +10:00
Dion Moult 5ee4e2704d User can now explicitly choose the quantity type via the edit qto API 2022-09-12 18:05:13 +10:00
Dion Moult 5b5540494d Qtos now support being added to types and contexts (despite being rare) 2022-09-12 12:55:15 +10:00
Dion Moult 85e123fadb IfcTester now supports occurs attributes on facets 2022-09-12 11:51:03 +10:00
Dion Moult f745222d37 IfcTester now supports mandatory measures for properties 2022-09-12 11:21:47 +10:00
Sigma Dimensions 88893a8c2c BBIM - Fix cost item products not loading for 1st cost item created 2022-09-11 22:14:57 +01:00
Dion Moult eb8bf60e93 Fix #2398. Add support for IFC4X3 entities. 2022-09-11 22:17:41 +10:00
Dion Moult 319aada33b Fix #2388. 2022-09-11 22:14:11 +10:00
Dion Moult 77c9bf5bb6 Implement newly proposed IDS PartOf facet 2022-09-11 17:20:50 +10:00
Dion Moult e5e81c818e IfcTester now handles floating point precision checks 2022-09-10 23:31:21 +10:00
Dion Moult 7aad0fc119 Implement type casting for property checks in IDS, bump to IDS v0.9 2022-09-10 22:42:22 +10:00
Sigma Dimensions f7403e8eee BBIM - Feature to guess all quantities at once 2022-09-09 13:25:04 +01:00
Dion Moult e2df4f335d API now supports adding representations using the Sverchok geometry structure 2022-09-09 22:20:06 +10:00
Sigma Dimensions cbe944d030 BBIM - temp fix to refresh cost items products' quantities UI, when qto is edited 2022-09-09 11:43:28 +01:00
bosonprojets 87f0475b99 Quality - run black on api/sequence directory 2022-09-09 02:25:20 +01:00
bosonprojets 4689cc858a BBIM Feature to import resources data from CSV 2022-09-09 02:23:53 +01:00
bosonprojets 6d6b893db2 Core tests for Resource module 2022-09-09 02:18:08 +01:00
bosonprojets e2d4a3ae24 Refactor Resource module 2022-09-09 02:17:28 +01:00
Dion Moult e4c1f7518d Revert "Hiding all objects in the "Types" Collection "
This reverts commit 2c0ea75960.
2022-09-09 10:59:10 +10:00
Dion Moult 48f8c0e085 Merge pull request #2395 from IfcOpenShell/Hiding-all-objects-in-the-"Types"-Collection
Hiding all objects in the "Types" Collection
2022-09-09 10:56:26 +10:00
Ryan Schultz 2c0ea75960 Hiding all objects in the "Types" Collection
When all the objects in the "Types" collection are hidden, it makes it quicker to turn on, and isolate one type for viewing and/or toggling other types on to compare to.
2022-09-08 13:12:47 -05:00
Thomas Krijnen e643972669 #2390 Submodule 2022-09-08 13:54:17 +02:00
Dion Moult d7c4936255 New recipe to fix dense TINs from 12D loading properly in Revit 2022-09-08 16:36:12 +10:00
Sigma Dimensions 96d19952e9 Quality check - run black on api/resource 2022-09-08 03:15:41 +01:00
Sigma Dimensions 57caf6a31f IfcOpenShell API: Deep purge resources upon removal 2022-09-08 03:12:44 +01:00
Dion Moult 189fc6e1e4 Minor fix 2022-09-07 20:39:55 +10:00
Dion Moult 88117941c5 Fix #2387. Bundling for Blender makefile now works on Mac. Thanks dirkolbrich! 2022-09-07 20:26:44 +10:00
Dion Moult 0a6c8a4343 Write docs on creating a single object 2022-09-07 20:14:08 +10:00
Christian Martínez de la Rosa 5bfad88a75 [IfcConvert] Added IfcRelConnectsElements to xml export (#2197)
* [IfcConvert] Export tasks in the xml conversion

The exportion in the XML format exports in a new tag, named "tasks", all the tasks from the file with a hierarchy. Each task contains the IfcTaskTime object if it has it.

* [ifcConvert] Add related objects in tasks

* [IfcConvert] Added IsSuccessorFrom and IsPredecessorTo in xml task export

* Get IfcTasks from IfcWorkSchedule

* [IfcConver] Changed OperatesOn hierarchy level in xml task export

* Added task to property / quanty relationship

* [IfcConvert] Added calendars to xml output file

* [IfcConvert] Added Output objects in the XML file

* [IfcConvert] Fix crash when IfcWorkCalendar does not contain IfcWorkTime

* [IfcConvert] Added IfcWorkPlan to xml export

* [IfcConvert] Added IfcRelConnectsElements to xml export

* [IfcConvert] Added IfcRelConnectsElements to xml export

* [IfcConvert] Improved related and relating xml export
2022-09-05 14:58:22 +02:00
Dion Moult 72544419da External resource relationships are now also purged when removing classifications 2022-09-05 16:51:42 +10:00
Dion Moult d1baaa21e7 Update IfcFM to use actors, not contacts, support federated datasets, and be more true to spec 2022-09-05 12:41:06 +10:00
Dion Moult 5d9fc44a69 Fix #2374. Axis for walls should be in the 2D plan context. 2022-09-05 11:35:04 +10:00
Dion Moult 917344ee1d Fix bug where new instances may have a wrong Z coordinate 2022-09-05 11:13:31 +10:00
Dion Moult 68ef6458d7 Fix #2379. Regressions where context adding never had any tests and failed. 2022-09-05 11:13:31 +10:00
Sigma Dimensions 36576c8d40 IFC resources utility: batch import Resource Data from CSV 2022-09-05 01:51:53 +01:00
Sigma Dimensions b44a1cb2e2 Fix 4d import context 2022-09-05 01:46:19 +01:00
Thomas Krijnen 5c7cf4469f Compute meaningful hash and eq on simple type instances 2022-09-04 14:23:35 +02:00
Dion Moult 147216cb7b Revert "Fix bug where IFC data types were hashed as the same value in RecycleNonRootedElements IfcPatch recipe"
This reverts commit 3be8e4f1ce.
2022-09-04 22:22:54 +10:00
Dion Moult 62b1478af1 Merge pull request #2377 from Martin15135215/v0.7.0
From append methode to use numpy and ifcpatch bug
2022-09-04 20:13:51 +10:00
Dion Moult 0f0055b0f1 Merge branch 'v0.7.0' into v0.7.0 2022-09-04 20:13:38 +10:00
Dion Moult 3be8e4f1ce Fix bug where IFC data types were hashed as the same value in RecycleNonRootedElements IfcPatch recipe 2022-09-04 20:12:54 +10:00
Martin15135215 6aa4ca89fe Merge branch 'IfcOpenShell:v0.7.0' into v0.7.0 2022-09-04 09:22:58 +00:00
Dion Moult 5fb574cf5c Fix #1589. Saving beveled curves as rswept disk solids is now supported. 2022-09-04 17:46:34 +10:00
Dion Moult 85a502a038 Support multiple swept disk solids with equal radii as a native Blender object 2022-09-04 17:45:26 +10:00
Dion Moult a7c56e742b Fix UI bug where adding nested groups closed the UI 2022-09-04 11:17:55 +10:00
Martin15135215 f11f648630 From append methode to do with numpy 2022-09-04 10:25:02 +10:00
Martin15135215 74d1bd7e3e Added "or element.is_a('IfcPropertySingleValue')" 2022-09-03 19:19:02 +02:00
Martin15135215 248ac71215 From append methode to use numpy 2022-09-03 19:13:55 +02:00
Dion Moult af4cb22756 Fix bug where appended assets sometimes duplicated geometry contexts 2022-09-03 22:16:35 +10:00
Dion Moult 56393ef358 Fix #170. Add note about not mixing OpenCascade installations. 2022-09-03 20:16:33 +10:00
Dion Moult 5d4512033d Fix #2286. Classifications can now be added for cost items. 2022-09-03 17:11:14 +10:00
Dion Moult b9cb1ab012 Black 2022-09-03 15:34:52 +10:00
Dion Moult e34b737428 Fix #2304. You can now manage classifications for materials. Bulk classification now supported. 2022-09-03 15:33:52 +10:00
Dion Moult 557d3429eb Support bulk deletion of psets on selected objects 2022-09-03 14:32:39 +10:00
Dion Moult f9019a4007 Test non-rooted classifications in IDS 2022-09-03 11:54:12 +10:00
Dion Moult 0668eadc36 IDS doc generator now also generates a sample IDS file 2022-09-03 10:48:46 +10:00
Dion Moult fdfaf54698 Support resource level classifications in IfcOpenShell API 2022-09-02 18:23:49 +10:00
Dion Moult ffa3aae132 Fix UI bug where changing classification levels sometimes didn't display 2022-09-02 15:21:27 +10:00
Dion Moult 0293c133ab Fix #2364. Bug where classifications couldn't be added in IFC2X3. 2022-09-02 14:03:05 +10:00
Dion Moult f9db685753 Fix #2343. Pset template applicability is refreshed when you save your templates. 2022-09-01 18:25:00 +10:00
Dion Moult a800d1af7e Fix pset template UI bug where changing to another prop didn't cancel editing older props. Also add support for blank or falsey enums (e.g. a real of 0.0). 2022-09-01 18:06:33 +10:00
Dion Moult f2bd362f57 Fix bug where adding a pset template didn't auto refresh the UI to the newly added template 2022-09-01 18:06:33 +10:00
krande 87021ebbe7 fix: edit ci triggers for ifcopenshell.
Changes are made to the github actions workflow files to reduce unnecessary triggering of ifcopenshell compilation, testing and conda package building
2022-09-01 09:41:30 +02:00
Dion Moult 36f2ed6da6 Allow disabling undo on save
The transaction system does not scale on large scale or complex remove_deep deletions. So if you want to delete a mesh with many thousands of vertices, it becomes very, very slow. Unfortunately we cannot also clear Blender's undo history, so this advanced feature should be used carefully.

Tested:

 - deleting 250 average objects: 264s before, 246s after (6.8% faster)
 - deleting 1 object with 6,000 vertices: 5 minutes and counting (basically impractical) before, 11s after
2022-09-01 11:17:06 +10:00
Dion Moult a61c084f27 Minor fix 2022-09-01 10:17:45 +10:00
Dion Moult 6849bb8c17 OpenCascade is not practically optional for now 2022-09-01 09:07:40 +10:00
Dion Moult a26f60861e Add pip/docker install instructions 2022-09-01 09:05:06 +10:00
Thomas Krijnen 106b8fdd07 Update Dockerfile 2022-08-31 21:01:09 +02:00
Thomas Krijnen 87c6b06cfc Update CMakeLists.txt 2022-08-31 20:48:36 +02:00
Thomas Krijnen af4917efba Sync dockerfile libs with ci libs 2022-08-31 20:28:26 +02:00
Dion Moult 806b2e2714 Add PyPi badge and switch to Shields badge for Anaconda
See https://github.com/ContinuumIO/anaconda-issues/issues/10231
2022-08-31 18:15:33 +10:00
Dion Moult 4ae8e4c74b Rename PyPi package to ifcopenshell-python due to naming conflict 2022-08-31 17:18:18 +10:00
Dion Moult 8101ffe06a Try using existing PYPI_TOKEN as the ifcopenshell package name is blocked 2022-08-31 16:54:27 +10:00
Dion Moult f07b6e14b4 Second time lucky? 2022-08-31 16:06:07 +10:00
Dion Moult f2c31cc17d Add optimistic Github action to upload wheels to PyPi 2022-08-31 16:02:39 +10:00
Dion Moult 39a626edeb Super simple wheel creator for PyPi 2022-08-31 15:41:01 +10:00
Sigma Dimensions 02379c5110 BBIM - New Sequence Toolkit Panel & new feature to highlight tasks for current selected object 2022-08-31 02:26:01 +01:00
Sigma Dimensions 1e661c42fb BBIM Feature - expand and contract all tasks in a work schedule 2022-08-31 02:21:28 +01:00
Thomas Krijnen eb7e9bacbf Update README.md 2022-08-30 21:58:11 +02:00
Thomas Krijnen c38e608a7c Update README.md 2022-08-30 21:49:51 +02:00
Dion Moult 544ed696da Add install instructions for ArchLinux for IfcOpenShell/IfcConvert 2022-08-30 23:00:18 +10:00
Dion Moult 322e47c5b5 Add badges to readme 2022-08-30 22:57:54 +10:00
Dion Moult 3ee9e6f45c Remove mention of outdated 3DS max from install instructions 2022-08-30 22:27:09 +10:00
Thomas Krijnen 7df94a906d #2305 fix cone boolean op 2022-08-30 13:31:26 +02:00
Dion Moult af1a40c913 Fix bug where if an object placement was reused elsewhere a circular reference was created 2022-08-30 20:08:50 +10:00
Dion Moult 451512ddff #1812 Add batching support for remove_deep2 for faster removals 2022-08-30 20:08:03 +10:00
Dion Moult fbd33145e1 Fix #2360. Fix documentation on IfcConvert flags that also apply to STP. 2022-08-30 18:28:30 +10:00
Dion Moult 8b0b1699f6 Fix #2370. Asset preview code did not clean up after itself properly after generating objects. 2022-08-30 16:21:25 +10:00
Dion Moult 2e13ed77d8 Fix #2371. Bug where you couldn't edit psets of a hidden object. 2022-08-30 11:38:04 +10:00
Dion Moult 804c0f4a77 Partially write tests for sequence refactor 2022-08-30 11:08:26 +10:00
Sigma Dimensions 37dec500fd massive refactor of the sequence module 2022-08-29 21:55:37 +01:00
krande ac3eb6fbda Merge remote-tracking branch 'origin/v0.7.0' into v0.7.0 2022-08-29 14:03:26 +02:00
krande 6a0bc0cd91 feature: align conda daily builds of ifcopenshell feature-wise with IfcOpenBot builds 2022-08-29 14:02:51 +02:00
Sigma Dimensions 699c8bf6b6 fix path windows test-bim 2022-08-29 12:51:18 +01:00
Dion Moult 7806f989f3 Minor fix 2022-08-29 21:47:12 +10:00
Dion Moult a0ca387ae7 Minor fix 2022-08-29 21:34:29 +10:00
Dion Moult 2a716ec242 Minor fix 2022-08-29 17:28:44 +10:00
Dion Moult 93ab51fb5e Fix #1958 - document other useful Blender add-ons 2022-08-29 16:25:17 +10:00
Dion Moult 7c59fc0b83 Document installation and usage of IfcConvert 2022-08-29 13:04:37 +10:00
Dion Moult 819466a819 Port over compilation instructions to Sphinx 2022-08-29 11:40:38 +10:00
Dion Moult 33a8ba9834 Add install instructions for Conda / Chocolatey 2022-08-29 09:14:26 +10:00
Ryan Schultz 6ce86ae368 Closing the ends of a swept_disk_solid by default 2022-08-29 08:49:46 +10:00
luz paz 68bf7a08e0 BlenderBIM: typo fixes 2022-08-28 11:39:15 +10:00
luz paz 771ba4c337 Revert 3rd party code changes 2022-08-27 15:34:16 +02:00
luz paz 33199b4bb6 Fix various typos 2022-08-27 15:34:16 +02:00
krande 74d730f368 [fix] - conda-build - set explicit env python path in conda build script for unix 2022-08-26 14:19:41 +02:00
Dion Moult eb2a71bf48 Fix #2159. Disable JUnit in Behave in Blender due to Etree conflicts. 2022-08-26 13:40:34 +10:00
Dion Moult 4b4e2e1622 Improve instructions on how to run tests and set up a dev environment 2022-08-26 13:40:34 +10:00
Thomas Krijnen b5f4c8218d #2358 fix header entity parsing in case of too few fields 2022-08-24 11:57:06 +02:00
Christian Kimmig e3a22601d5 Set Position of extrusions with height = 0 2022-08-24 11:10:45 +02:00
Dion Moult f67856a2ae Minor fix 2022-08-24 10:13:30 +10:00
Dion Moult 6b71b32e63 Improve documentation for running tests 2022-08-24 09:54:15 +10:00
Dion Moult 60d99fe2fa Update dev install process docs and bump IfcOpenShell dep 2022-08-23 17:15:11 +10:00
Dion Moult 75e1786682 Purge IDS features from BIMTester as superseded by IfcTester 2022-08-23 16:37:15 +10:00
Dion Moult 9b3f964000 Fix #2060. Large lists of types in a library can now be filtered. 2022-08-23 13:26:38 +10:00
Dion Moult de6b578d9c Fix #1375. Spatial zones as non-hierarchical entities are now supported. 2022-08-22 17:31:44 +10:00
Dion Moult f50e790b11 Minor fix 2022-08-22 17:02:05 +10:00
Dion Moult 9821c3af67 Fix #2317 add graphical support for viewing and editing referenced structures 2022-08-22 13:52:12 +10:00
Dion Moult 0844d6f52e Fix #2354 - add support for selector queries for enumerated properties 2022-08-19 20:10:54 +10:00
Dion Moult 520e7653e4 #1375 #2317 add API support for referencing in spatial structures 2022-08-19 18:43:39 +10:00
Dion Moult 9512075506 Minor fix 2022-08-19 12:41:08 +10:00
Dion Moult bdd61713ea WIP refactor sequence module 2022-08-19 11:20:18 +10:00
Dion Moult 97cf1da4ff #2339 Read directly from raw data instead of using strings for IfcDiff output 2022-08-18 17:57:11 +10:00
Dion Moult 836a162d57 #2342 Support different IFC schema versions for obj2ifc 2022-08-18 17:15:39 +10:00
Dion Moult 09a6f56f47 #2319 Add documentation for IfcDiff 2022-08-18 16:12:10 +10:00
Ryan Schultz 35ce8c2848 Updated links to point to current branch 2022-08-17 16:12:35 -05:00
Dion Moult ae2fd67260 #2342 New Meshlab based OBJ2IFC converter 2022-08-17 14:33:48 +10:00
Dion Moult 9def041f27 You can now create multiple new IDSes side by side and close them 2022-08-16 17:14:54 +10:00
Dion Moult e9d86253ec Minor fixes to IfcTester 2022-08-16 11:52:01 +10:00
Dion Moult f95466ee0c IfcTester now integrated into BlenderBIM Add-on UI 2022-08-16 11:52:01 +10:00
Dion Moult e85f21ce7a Minor fix 2022-08-16 11:52:01 +10:00
Sigma Dimensions d3795c9836 Documentation for ifcopenshell.util.element 2022-08-15 19:40:51 +01:00
krande ab647b13d9 fix ci initializer to prevent accidentally triggering and add tagged release for conda-forge release 2022-08-15 16:59:32 +02:00
admin f794b647e1 merge v0.6.0 2021-12-06 09:59:18 +08:00
admin 2e81b2bd17 merage v0.6
ADD IFCGroup parse #1154
2021-12-02 10:19:47 +08:00
admin 8527d0fcbd merage new 0.6 2021-12-01 10:32:20 +08:00
admin bc32481185 merge 2021-07-05 10:30:06 +08:00
admin 58e9f1baf9 merage 2021-05-10 14:06:18 +08:00
admin db5432d902 merge master v0.6.0 2021-05-06 09:54:33 +08:00
admin 397b6ae5fe merage master v6.0.0 2021-04-12 10:26:10 +08:00
admin 72fdca9e82 merage 2021-04-01 16:41:28 +08:00
admin 29b8e67bc8 merge all 2021-03-25 10:04:25 +08:00
admin 33ec80e54d merge update 2021-01-28 13:56:47 +08:00
admin 371e4137f1 update mager 2021-01-21 13:43:19 +08:00
admin 555a2ce79e merge master v6.0 2021-01-11 11:39:36 +08:00
admin 699742880e fix IFC to xml - IFCGROUP in IFC cannot be found in xml #1154 2021-01-11 10:59:03 +08:00
GisSpace d99348f163 Merge pull request #3 from IfcOpenShell/v0.6.0
merge master
2020-12-31 15:44:04 +08:00
GisSpace d7034c6a40 Merge pull request #2 from IfcOpenShell/v0.6.0
merge
2020-12-21 11:13:59 +08:00
GisSpace ac632d0698 Merge pull request #1 from IfcOpenShell/v0.6.0
同步源库
2020-12-18 16:23:04 +08:00
admin f3696788cb #1097
fix:IFC file conversion obj file, some cases will be rotated 90 °
2020-12-10 18:44:23 +08:00
1067 changed files with 602750 additions and 18779 deletions
+5 -6
View File
@@ -19,20 +19,19 @@ jobs:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
with:
python-version: '3.x'
python-version: '3.10'
- name: Build package
run: |
cd src/bcf
pip install build
python -m build
- name: Test
run: |
cd src/bcf
make test
- name: Publish package
uses: pypa/gh-action-pypi-publish@release/v1
with:
user: __token__
password: ${{ secrets.PYPI_TOKEN }}
packages_dir: src/bcf/dist
+9 -15
View File
@@ -8,7 +8,7 @@ on:
# │ │ │ ┌───────────── month (1 - 12 or JAN-DEC)
# │ │ │ │ ┌───────────── day of the week (0 - 6 or SUN-SAT)
# * * * * *
- cron: "55 23 * * *" # 5min before utc midnight
- cron: "30 0 * * *" # 30min past utc midnight
env:
major: 0
@@ -20,6 +20,8 @@ env:
jobs:
activate:
runs-on: ubuntu-latest
if: |
github.repository == 'IfcOpenShell/IfcOpenShell'
steps:
- name: Set env
run: echo ok go
@@ -41,7 +43,7 @@ jobs:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2 # https://github.com/actions/setup-python
with:
python-version: '3.7.7' # Version range or exact version of a Python version to use, using SemVer's version range syntax
python-version: '3.10.9' # Version range or exact version of a Python version to use, using SemVer's version range syntax
architecture: 'x64' # optional x64 or x86. Defaults to x64 if not specified
- run: echo ${{ env.DATE }}
@@ -50,19 +52,10 @@ jobs:
run: |
echo "::set-output name=choco_release::$(python3 /home/runner/work/IfcOpenShell/IfcOpenShell/choco/blenderbim/check_repo_infos.py --do_choco_release?)"
- name: Compile
if: ${{steps.do_choco.outputs.choco_release}} == 'do_choco_release'
run: |
target_os=${{ matrix.config.short_name }} &&
pyver=$(python3 /home/runner/work/IfcOpenShell/IfcOpenShell/choco/blenderbim/check_repo_infos.py --pyver?) &&
cp -r src/blenderbim src/blenderbim_$target_os_$pyver &&
cd src/blenderbim_$target_os_$pyver &&
make dist PLATFORM=$target_os PYVERSION=$pyver
- name: Fill chocolatey scripts on win with latest blender python
if: ${{steps.do_choco.outputs.choco_release}} == 'do_choco_release'
run: |
today_non_iso="$(date +'%y%m%d' | tr -d '\n')" &&
yesterday_non_iso="$(date --date='yesterday' +'%y%m%d' | tr -d '\n')" &&
target_os=${{ matrix.config.short_name }} &&
latest_blender_python_version_maj_min=$(python3 /home/runner/work/IfcOpenShell/IfcOpenShell/choco/blenderbim/check_repo_infos.py --latest_blender_python_version_maj_min?) &&
echo "latest_blender_python_version_maj_min?: $latest_blender_python_version_maj_min" &&
@@ -70,9 +63,10 @@ jobs:
export latest_blender_version_maj_min=$(python3 /home/runner/work/IfcOpenShell/IfcOpenShell/choco/blenderbim/check_repo_infos.py --latest_blender_release_maj_min?) &&
export latest_blender_version_maj_min_pat=$(python3 /home/runner/work/IfcOpenShell/IfcOpenShell/choco/blenderbim/check_repo_infos.py --latest_blender_release_maj_min_pat?) &&
echo latest_blender_version_maj_min_pat?: $latest_blender_version_maj_min_pat &&
export blenderbim_build_version="${{ env.major }}.${{ env.minor }}.$today_non_iso" &&
export url_blenderbim_py310_win_zip="https://github.com/IfcOpenShell/IfcOpenShell/releases/download/blenderbim-$today_non_iso/blenderbim-$today_non_iso-$pyver-$target_os.zip" &&
export sha256sum_blenderbim_py310_win_zip=$( sha256sum src/blenderbim_$target_os_$pyver/dist/blenderbim-$today_non_iso-$pyver-$target_os.zip --tag | cut -d ' ' -f 4 | tr -d '\n') &&
export blenderbim_build_version="${{ env.major }}.${{ env.minor }}.$yesterday_non_iso" &&
export url_blenderbim_py310_win_zip="https://github.com/IfcOpenShell/IfcOpenShell/releases/download/blenderbim-$yesterday_non_iso/blenderbim-$yesterday_non_iso-$pyver-$target_os.zip" &&
wget $url_blenderbim_py310_win_zip --no-verbose &&
export sha256sum_blenderbim_py310_win_zip=$( sha256sum blenderbim-$yesterday_non_iso-$pyver-$target_os.zip --tag | cut -d ' ' -f 4 | tr -d '\n') &&
echo sha256sum_blenderbim_py310_win_zip: $sha256sum_blenderbim_py310_win_zip &&
python3 choco/blenderbim/fill_dynamic_parameters.py &&
echo __build choco with mono &&
+4 -1
View File
@@ -53,11 +53,14 @@ jobs:
name: "MacOS Build",
short_name: macos
}
- {
name: "MacOS ARM Build",
short_name: macosm1
}
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2 # https://github.com/actions/setup-python
with:
python-version: '3.7.7' # Version range or exact version of a Python version to use, using SemVer's version range syntax
architecture: 'x64' # optional x64 or x86. Defaults to x64 if not specified
- run: echo ${{ env.DATE }}
- name: Get current date
@@ -1,22 +1,23 @@
name: ci-ifcopenshell-daily
name: ci-ifcopenshell-conda-daily
on:
push:
paths:
- src/**
- conda/**
- .github/workflows/ci-daily-build.yml
branches:
- pr-daily-builds
# Only trigger, when the build workflow succeeded
workflow_run:
workflows: ["ci"]
types:
- completed
jobs:
activate:
runs-on: ubuntu-latest
if: |
github.repository == 'IfcOpenShell/IfcOpenShell'
steps:
- name: Set env
run: echo ok go
test:
name: ${{ matrix.platform.distver }}-${{ matrix.pyver.name }}
needs: activate
runs-on: ${{ matrix.platform.distver }}
defaults:
run:
@@ -25,16 +26,17 @@ jobs:
fail-fast: false
matrix:
pyver: [
{ name: py39, distver: '3.9' },
{ name: py310, distver: '3.10'}
# { name: py39, distver: '3.9' },
{ name: py310, distver: '3.10'},
{ name: py311, distver: '3.11'}
]
platform: [
{ name: Windows, distver: windows-2022, upload: 'true' },
{ name: Windows, distver: windows-2019, upload: 'false' },
{ name: Linux, distver: ubuntu-20.04, upload: 'false' },
{ name: Linux, distver: ubuntu-18.04, upload: 'true' },
{ name: Windows, distver: windows-2022, upload: 'false' },
{ name: Windows, distver: windows-2019, upload: 'true' },
{ name: Linux, distver: ubuntu-20.04, upload: 'true' },
{ name: Linux, distver: ubuntu-22.04, upload: 'false' },
{ name: macOS, distver: macos-11, upload: 'true' },
{ name: macOS, distver: macos-10.15, upload: 'false' }
{ name: macOS, distver: macos-12, upload: 'false' }
]
steps:
- uses: actions/checkout@v2
@@ -60,4 +62,4 @@ jobs:
- name: build & test ifcopenshell
if: ${{ matrix.platform.upload == 'false' }}
run: |
conda-build . --python ${{ matrix.pyver.distver }} -c conda-forge --no-remove-work-dir
conda-build . --python ${{ matrix.pyver.distver }} -c conda-forge --no-remove-work-dir
@@ -0,0 +1,73 @@
name: ci-ifcopenshell-pypi
on:
schedule:
# ┌───────────── minute (0 - 59)
# │ ┌───────────── hour (0 - 23)
# │ │ ┌───────────── day of the month (1 - 31)
# │ │ │ ┌───────────── month (1 - 12 or JAN-DEC)
# │ │ │ │ ┌───────────── day of the week (0 - 6 or SUN-SAT)
# * * * * *
- cron: "49 23 1 * *" # 11min before utc midnight
env:
major: 0
minor: 0
name: ifcopenshell
jobs:
activate:
runs-on: ubuntu-latest
if: |
github.repository == 'IfcOpenShell/IfcOpenShell'
steps:
- name: Set env
run: echo ok go
build:
needs: activate
name: ${{ matrix.config.name }}-${{ matrix.pyver }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
pyver: [py36, py37, py38, py39, py310, py311]
config:
- {
name: "Windows Build",
short_name: win,
}
- {
name: "Linux Build",
short_name: linux
}
- {
name: "MacOS Build",
short_name: macos
}
- {
name: "MacOS ARM Build",
short_name: macosm1
}
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2 # https://github.com/actions/setup-python
with:
python-version: '3.7.7' # Version range or exact version of a Python version to use, using SemVer's version range syntax
architecture: 'x64' # optional x64 or x86. Defaults to x64 if not specified
- run: echo ${{ env.DATE }}
- name: Get current date
id: date
run: echo "::set-output name=date::$(date +'%y%m%d')"
- name: Compile
run: |
pip install build
cp -r src/ifcopenshell-python src/ifcopenshell_${{ matrix.config.short_name }}_${{ matrix.pyver }} &&
cd src/ifcopenshell_${{ matrix.config.short_name }}_${{ matrix.pyver }} &&
make dist PLATFORM=${{ matrix.config.short_name }} PYVERSION=${{ matrix.pyver }}
- name: Publish a Python distribution to PyPI
uses: ortega2247/pypi-upload-action@master
with:
user: __token__
password: ${{ secrets.PYPI_API_TOKEN }}
packages_dir: src/ifcopenshell_${{ matrix.config.short_name }}_${{ matrix.pyver }}/dist
+1
View File
@@ -85,6 +85,7 @@ jobs:
sudo /usr/bin/python -m pip install isodate
sudo /usr/bin/python -m pip install lark
sudo /usr/bin/python -m pip install networkx
sudo /usr/bin/python -m pip install tabulate
- name: Test
run: |
+14 -2
View File
@@ -3,8 +3,19 @@ name: ci
on:
push:
paths:
- 'src/**'
- 'test/**'
- 'src/ifcblender/**'
- 'src/ifcconvert/**'
- 'src/ifcgeom/**'
- 'src/ifcgeom_schema_agnostic/**'
- 'src/ifcgeomserver/**'
- 'src/ifcjni/**'
- 'src/ifcmax/**'
- 'src/ifcopenshell-python/**'
- '!src/ifcopenshell-python/docs/**'
- 'src/ifcparse/**'
- 'src/ifcwrap/**'
- 'src/qtviewer/**'
- 'src/serializers/**'
- 'conda/**'
- 'cmake/**'
- '.github/workflows/ci.yml'
@@ -84,6 +95,7 @@ jobs:
sudo /usr/bin/python -m pip install isodate
sudo /usr/bin/python -m pip install lark
sudo /usr/bin/python -m pip install networkx
sudo /usr/bin/python -m pip install tabulate
- name: Test
run: |
+14
View File
@@ -73,3 +73,17 @@ _build/
# IDS Docs
src/ifcopenshell-python/test/build
# tox cache
.tox/
*.egg-info/
# mypy cache
.mypy_cache
# blenderbim libs
src/blenderbim/blenderbim/libs
# ifcopenshell swig and compiled files
src/ifcopenshell-python/ifcopenshell/_ifcopenshell_wrapper.so
src/ifcopenshell-python/ifcopenshell/ifcopenshell_wrapper.py
+5 -3
View File
@@ -23,9 +23,11 @@ RUN echo "deb http://archive.ubuntu.com/ubuntu focal-proposed main restricted" |
echo "deb http://archive.ubuntu.com/ubuntu focal-proposed multiverse" | tee -a /etc/apt/sources.list; \
apt-get -qq update; \
apt-get -y install tzdata dos2unix rsync; \
apt-get -y install python3 libxml2 liboce-foundation11 liboce-modeling11 liboce-ocaf11 liboce-visualization11 \
liboce-ocaf-lite11 libpython3.8 libboost-system1.67.0 libboost-program-options1.67.0 \
libboost-regex1.67.0 libboost-thread1.67.0 libboost-date-time1.67.0; \
apt-get -y install python3 libxml2 libpython3.8 \
libboost-all-dev \
libocct-foundation-dev libocct-modeling-algorithms-dev libocct-modeling-data-dev \
libocct-ocaf-dev libocct-visualization-dev libocct-data-exchange-dev \
libhdf5-serial-dev ; \
rm -rf /var/lib/apt/lists/* ;
COPY . /home/IfcOpenShell/
+60 -293
View File
@@ -1,307 +1,74 @@
IfcOpenShell
============
IfcOpenShell is an open source ([LGPL]) software library for working with the Industry Foundation Classes ([IFC])
file format. Extensive geometric support is implemented for the IFC releases [IFC2x3 TC1] and [IFC4 Add2 TC1].
Support for parsing is provided for IFC4x1, IFC4x2, and the IFC4x3 release candidates. Extending with support for
arbitrary IFC schemas is possible at compile-time when using C++ and at run-time when using Python.
For more information, see
* [http://ifcopenshell.org](http://ifcopenshell.org)
* [http://academy.ifcopenshell.org](http://academy.ifcopenshell.org)
IfcOpenShell is an open source ([LGPL]) software library for working with Industry Foundation Classes ([IFC]). Complete
parsing support is provided for [IFC2x3 TC1], [IFC4 Add2 TC1], IFC4x1, IFC4x3, and IFC4x3. Extensive geometric support
is implemented for the IFC releases [IFC2x3 TC1] and [IFC4 Add2 TC1]. Extending with support for arbitrary IFC schemas
is possible at compile-time when using C++ and at run-time when using Python.
[![Build Status](https://travis-ci.com/IfcOpenShell/IfcOpenShell.svg?branch=v0.6.0)](https://travis-ci.com/IfcOpenShell/IfcOpenShell)
In addition to a C++ and Python API, IfcOpenShell comes with an ecosystem of tools, notably including IfcConvert (an application to convert IFC models to
other formats), the BlenderBIM Add-on (an add-on to Blender providing a graphical IFC authoring platform), and many
other libraries, CLI apps, and more. Support is also provided for auxiliary standards such as BCF and IDS.
[![Financial Contributors](https://opencollective.com/opensourcebim/tiers/badge.svg)](https://opencollective.com/opensourcebim/)
For more information, see:
Prerequisites
-------------
* Git
* CMake (3.1.3 or newer)
* Windows: [Visual Studio] 2008 to 2019 (2022 not yet supported by dependency CMake) with C++ toolset (or [Visual C++ Build Tools]) or [MSYS2] + MinGW
* *nix: GCC 4.7 or newer, or Clang (any version)
* [IfcOpenShell Website](http://ifcopenshell.org)
* [IfcOpenShell Documentation](http://blenderbim.org/docs-python)
* [IfcOpenShell C++ Installation](https://blenderbim.org/docs-python/ifcopenshell/installation.html)
* [IfcOpenShell Python Installation](https://blenderbim.org/docs-python/ifcopenshell-python/installation.html)
* [IfcOpenShell Python Hello World Tutorial](https://blenderbim.org/docs-python/ifcopenshell-python/hello_world.html)
* [BlenderBIM Add-on Website](https://blenderbim.org)
* [BlenderBIM Add-on Documentation](http://blenderbim.org/docs)
* [Add-on Installation](https://blenderbim.org/docs/users/installation.html)
* [Exploring an IFC model](https://blenderbim.org/docs/users/exploring_an_ifc_model.html)
Dependencies
-------------
* [Boost](http://www.boost.org/)
* [Open Cascade](https://dev.opencascade.org/) - *optional*, but required for building IfcGeom
([official](https://dev.opencascade.org/release), "OCCT", or [community edition](https://github.com/tpaviot/oce), "OCE")
For converting IFC representation items into BRep solids and tesselated meshes
* [OpenCOLLADA](https://github.com/khronosGroup/OpenCOLLADA/) - *optional*
For IfcConvert to be able to write tessellated Collada (.dae) files
* [SWIG](http://www.swig.org/) and [Python](https://www.python.org/) - *optional*
For building the IfcOpenShell Python interface and the Blender add-on
* [3ds Max SDK](http://www.autodesk.com/products/3ds-max/free-trial) - *optional*
For building the 3ds Max plug-in.
All recent versions of 3ds Max (2014 and newer) are 64-bit only, so a 64-bit installation is assumed.
| Service | Status |
| ----------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
| Anaconda Daily Build | [![Anaconda-Server Badge](https://img.shields.io/conda/vn/ifcopenshell/ifcopenshell)](https://anaconda.org/ifcopenshell/ifcopenshell) |
| Anaconda v0.7.0 Stable | [![Anaconda-Server Badge](https://img.shields.io/conda/vn/conda-forge/ifcopenshell)](https://anaconda.org/conda-forge/ifcopenshell) |
| PyPi Daily Build | [![PyPi Badge](https://img.shields.io/pypi/v/ifcopenshell)](https://pypi.org/project/ifcopenshell/) |
| ArchLinux AUR Package Stable | [![AUR Badge](https://img.shields.io/aur/version/ifcopenshell)](https://aur.archlinux.org/packages/ifcopenshell) |
| ArchLinux AUR Package git | [![AUR Badge](https://img.shields.io/aur/version/ifcopenshell-git)](https://aur.archlinux.org/packages/ifcopenshell-git) |
| BlenderBIM Add-on Chocolatey (under moderation) | [![Chocolatey Badge](https://img.shields.io/chocolatey/v/blenderbim-nightly)](https://community.chocolatey.org/packages/blenderbim-nightly/) |
| Sponsor development on OpenCollective | [![Financial Contributors](https://opencollective.com/opensourcebim/tiers/badge.svg)](https://opencollective.com/opensourcebim/) |
| Docker hub | [![Docker Pulls](https://img.shields.io/docker/pulls/aecgeeks/ifcopenshell)](https://hub.docker.com/r/aecgeeks/ifcopenshell) |
Building IfcOpenShell
---------------------
Note 1: The path where the source code is cloned to can contain spaces but non-ASCII characters are very likely to cause problems with the build.
Note 2: If you had not used `git clone --recursive https://github.com/IfcOpenShell/IfcOpenshell.git`, update the submodules by running `git submodule init & git submodule update`.
Note 3: Be careful with special characters is the path when using the nix or win build scripts, because the OpenCASCADE build will fail on paths containing ++ and likely other situations.
### Compiling on Windows
The preferred way to fetch and build this project's dependencies is to use the build scripts
in win/ folder. **See [win/readme.md] for more information**.
#### Using Visual Studio
Instructions in a nutshell (**assuming Visual Studio 2015 x64 environment variables set**):
> cd IfcOpenShell\win
> build-deps.cmd
> run-cmake.bat
NB: `build-deps.cmd` need to be ran from the directory containing it, i.e. the `./win` folder.
You can now open and build the solution file in Visual Studio:
> ..\build-vs2015-x64\IfcOpenShell.sln
As the scripts default to using the `RelWithDebInfo` configuration, and a freshly created solution by CMake defaults
to `Debug`, make sure to switch the used build configuration. Build the `INSTALL` project (right-click -> Project
Only) to deploy the headers and binaries into a single location if wanted/needed.
Alternatively, one can use the utility batch file(s) to build and install the project easily from the command-line
(installing a project will build it also, if required):
> install-ifcopenshell.bat
#### Using MSYS2 + MinGW
Start the MSYS2 Shell and then:
$ cd IfcOpenShell/win
$ ./build-deps.sh
$ ./run-cmake.sh
$ ./install-ifcopenshell.sh
#### Using Bash on Ubuntu on Windows
Start Bash on Ubuntu on Windows and follow the instructions below. Compiling on Ubuntu 14.04.4 LTS using GCC 4.8.4
or Clang 3.5 has been confirmed to work.
### Compiling on *nix
The following instructions are for Ubuntu, modify as required for other operating systems. [nix/build-all.py] script
can be experimented with and studied for pointers for other operating systems, but note that this script is not currently
meant to be used for a typical IfcOpenShell workspace setup.
Note 1: It is recommeded to use OCCT for IfcOpenShell. You could use OCE as well, but sometimes it may lag behind OCCT and
therefore not compile with the latest IfcOpenShell.
Note 2: where `make -j` is written, add a number roughly equal to the amount of CPU cores + 1.
**1)** Install most of the prerequisites and dependencies:
$ sudo apt-get install git cmake gcc g++ libboost-all-dev libcgal-dev
**2a)** Either use an OCCT package from your operating system's software repository
$ sudo apt-get install libocct-data-exchange-dev libocct-draw-dev libocct-foundation-dev libocct-modeling-algorithms-dev libocct-modeling-data-dev libocct-ocaf-dev libocct-visualization-dev
**2b)** or, if OCCT is not available or the latest code is wanted, obtain and compile OCCT from https://dev.opencascade.org/release
**2c)** or, if you'd like to try using OCE, use the package from your operating system's software repository
$ sudo apt-get install liboce-foundation-dev liboce-modeling-dev liboce-ocaf-dev liboce-visualization-dev liboce-ocaf-lite-dev
**2d)** or if OCE is not available, or the latest code is wanted, compile OCE yourself (note that the build takes a long time)
$ sudo apt-get install libftgl-dev libtbb2 libtbb-dev libgl1-mesa-dev libfreetype6-dev
$ git clone https://github.com/tpaviot/oce.git
$ cd oce
$ mkdir build && cd build
$ cmake ..
$ make -j
$ sudo make install
**3)** For building IfcConvert with COLLADA (.dae) support (on by default), OpenCOLLADA is needed:
$ sudo apt-get install libpcre3-dev libxml2-dev
$ git clone https://github.com/KhronosGroup/OpenCOLLADA.git
$ cd OpenCOLLADA
Using a known good revision, but HEAD should work too:
$ git checkout 064a60b65c2c31b94f013820856bc84fb1937cc6
$ mkdir build && cd build
$ cmake ..
$ make -j
$ sudo make install
**4)** For building the IfcPython wrapper (on by default), SWIG and Python development are needed, if not already available:
$ sudo apt-get install python-all-dev swig
**5)** To build IfcOpenShell please take the following steps. Alternatively use environment variables for setting the
dependencies' paths. `OCC_INCLUDE_DIR` might be needed to set also. `OPENCOLLADA_INCLUDE_DIR` and `OPENCOLLADA_LIBRARY_DIR`
(and potentially `PCRE_LIBRARY_DIR`) are needed if building with COLLADA support. (`-DCOLLADA_SUPPORT=0` disables it).
$ cd /path/to/IfcOpenShell
$ mkdir build && cd build
$ cmake ../cmake -DOCC_LIBRARY_DIR=/usr/lib/x86_64-linux-gnu/ \
-DOPENCOLLADA_INCLUDE_DIR="/usr/local/include/opencollada" \
-DOPENCOLLADA_LIBRARY_DIR="/usr/local/lib/opencollada" \
-DPCRE_LIBRARY_DIR=/usr/lib/x86_64-linux-gnu/ \
-DCGAL_INCLUDE_DIR=/usr/include \
-DGMP_INCLUDE_DIR=/usr/include \
-DMPFR_INCLUDE_DIR=/usr/include \
-DGMP_LIBRARY_DIR=/usr/lib/x86_64-linux-gnu \
-DMPFR_LIBRARY_DIR=/usr/lib/x86_64-linux-gnu \
-DHDF5_SUPPORT=Off
$ make -j
If all worked out correctly you can now use IfcOpenShell. See the examples below.
**6)** Install the project if wanted:
$ sudo make install
### Installing on MacOS Using Homebrew
**1)** Install all dependencies using [Homebrew](https://brew.sh/)
```{shell}
$ brew install boost swig cmake ftgl cgal gmp libaec opencascade
```
**2)** Clone the git repo and its submodules
```{shell}
$ git clone --recurse-submodules https://github.com/IfcOpenShell/IfcOpenShell.git
```
**3)** Build IfcOpenShell with flags for Homebrew dependencies (```/usr/local/```)
```{shell}
$ cd /path/to/IfcOpenShell
$ mkdir build && cd build
$ cmake ../cmake -DOCC_LIBRARY_DIR=/usr/local/lib/ \
-DOCC_INCLUDE_DIR=/usr/local/include/opencascade/ \
-DCOLLADA_SUPPORT=0 \
-DCGAL_INCLUDE_DIR=/usr/local/include/ \
-DGMP_LIBRARY_DIR=/usr/local/lib/ \
-DMPFR_LIBRARY_DIR=/usr/local/lib/
$ make -j -lboost_options
```
Note: Make sure to compile using XCode, rather than a ```brew``` installed C/C++ compiler.
Installing IfcOpenShell with Conda
----------------------------------
Another option for building and installing IfcOpenShell is to use the popular
[Anaconda Python Distribution](https://www.anaconda.com/download).
The requirements are spread across a number of channels.
You can add these channels to your configuration, or specify them all on the command line:
$ conda install -c conda-forge -c oce -c dlr-sc -c ifcopenshell ifcopenshell
Usage examples
--------------
**Invoking IfcConvert from the command line**
$ wget -O duplex.zip https://portal.nibs.org/files/wl/\?id=4DsTgHFQAcOXzFetxbpRCECPbbfUqpgo
$ unzip duplex.zip
$ ./IfcConvert Duplex_A_20110907_optimized.ifc
$ less Duplex_A_20110907_optimized.obj
**Using the IfcOpenShell Python interface**
$ wget -O duplex.zip https://portal.nibs.org/files/wl/\?id=4DsTgHFQAcOXzFetxbpRCECPbbfUqpgo
$ unzip duplex.zip
$ python
>>> import ifcopenshell
>>> f = ifcopenshell.open("Duplex_A_20110907_optimized.ifc")
>>>
>>> # Accessing entity instances by type:
>>> f.by_type("ifcwall")[:2]
[#91=IfcWallStandardCase('2O2Fr$t4X7Zf8NOew3FL9r',#1,'Basic Wall:Interior - Partition (92mm Stud):144586',$,'Basic Wall:Interior - Partition (92mm Stud):128360',#5198,#18806,'144586'), #92=IfcWallStandardCase('2O2Fr$t4X7Zf8NOew3FLIE',#1,'Basic Wall:Interior - Partition (92mm Stud):143921',$,'Basic Wall:Interior - Partition (92mm Stud):128360',#5206,#18805,'143921')]
>>> wall = _[0]
>>> len(wall) # number of EXPRESS attributes
8
>>>
>>> # Accessing EXPRESS attributes by name:
>>> wall.GlobalId
'2O2Fr$t4X7Zf8NOew3FL9r'
>>> wall.Name = "My wall"
>>> wall.NonExistingAttr
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File ".\ifcopenshell.py", line 14, in __getattr__
except: raise AttributeError("entity instance of type '%s' has no attribute'%s'"%(self.wrapped_data.is_a(), name)) from None
AttributeError: entity instance of type 'IfcWallStandardCase' has no attribute 'NonExistingAttr'
>>> wall.GlobalId = 3
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File ".\ifcopenshell.py", line 26, in __setattr__
self[self.wrapped_data.get_argument_index(key)] = value
File ".\ifcopenshell.py", line 30, in __setitem__
self.wrapped_data.set_argument(idx, entity_instance.map_value(value))
File ".\ifc_wrapper.py", line 118, in <lambda>
set_argument = lambda self,x,y: self._set_argument(x) if y is None else self
._set_argument(x,y)
File ".\ifc_wrapper.py", line 114, in _set_argument
def _set_argument(self, *args): return _ifc_wrapper.entity_instance__set_argument(self, *args)
RuntimeError: INT is not a valid type for 'GlobalId'
>>> # Creating new entity instances
>>> f.createIfcCartesianPoint(Coordinates=(1.0,1.5,2.0))
#27530=IfcCartesianPoint((1.,1.5,2.))
>>>
>>> # Working with GlobalId attributes:
>>> import uuid
>>> ifcopenshell.guid.compress(uuid.uuid1().hex)
'3x4C8Q_6qHuv$P$FYkANRX'
>>> new_guid = _
>>> owner_hist = f.by_type("IfcOwnerHistory")[0]
>>> new_wall = f.createIfcWallStandardCase(new_guid, owner_hist, None, None, Tag='my_tag')
>>> new_wall.ObjectType = ''
>>> new_wall.ObjectPlacement = new_wall.Representation = None
>>>
>>> # Accessing entity instances by instance id or GlobalId:
>>> f[92]
#92=IfcWallStandardCase('2O2Fr$t4X7Zf8NOew3FLIE',#1,'Basic Wall:Interior - Partition (92mm Stud):143921',$,'Basic Wall:Interior - Partition (92mm Stud):128360',#5206,#18805,'143921')
>>> f['2O2Fr$t4X7Zf8NOew3FLIE']
#92=IfcWallStandardCase('2O2Fr$t4X7Zf8NOew3FLIE',#1,'Basic Wall:Interior - Partition (92mm Stud):143921',$,'Basic Wall:Interior - Partition (92mm Stud):128360',#5206,#18805,'143921')
>>>
>>> # Writing IFC-SPF files to disk:
>>> f.write("out.ifc")
Extra tools
-----------
Also available are a series of utilities that are based on or related to IfcOpenShell.
Contents
--------
Those marked with an asterisk are part of IfcOpenShell.
Name | License
--- | ---
bcf | LGPL-3.0-or-later
blenderbim | GPL-3.0-or-later
bsdd | LGPL-3.0-or-later
ifc2ca | LGPL-3.0-or-later
ifc4d | LGPL-3.0-or-later
ifc5d | LGPL-3.0-or-later
ifcbimtester | LGPL-3.0-or-later
ifcblender | LGPL-3.0-or-later\*
ifccityjson | LGPL-3.0-or-later
ifcclash | LGPL-3.0-or-later
ifccobie | LGPL-3.0-or-later
ifcconvert | LGPL-3.0-or-later\*
ifccsv | LGPL-3.0-or-later
ifcdiff | LGPL-3.0-or-later
ifcfm | LGPL-3.0-or-later
ifcgeom | LGPL-3.0-or-later\*
ifcgeom\_schema\_agnostic | LGPL-3.0-or-later\*
ifcgeomserver | LGPL-3.0-or-later\*
ifcjni | LGPL-3.0-or-later\*
ifcmax | LGPL-3.0-or-later\*
ifcopenshell-python | LGPL-3.0-or-later\*
ifcparse | LGPL-3.0-or-later\*
ifcpatch | LGPL-3.0-or-later
ifcsverchok | GPL-3.0-or-later
ifcwrap | LGPL-3.0-or-later\*
qtviewer | LGPL-3.0-or-later\*
serializers | LGPL-3.0-or-later\*
| Name | Description | License |
| ------------------------- | ----------------------------------------------------------------------| ------------------- |
| bcf | Library to read and write BCF-XML and query OpenCDE BCF-API modules | LGPL-3.0-or-later |
| blenderbim | Add-on to Blender providing a graphical native IFC authoring platform | GPL-3.0-or-later |
| bsdd | Library to query the bSDD API | LGPL-3.0-or-later |
| ifc2ca | Utility to convert IFC structural analysis models to Code_Aster | LGPL-3.0-or-later |
| ifc4d | Convert to and from IFC and project management software | LGPL-3.0-or-later |
| ifc5d | Report and optimise cost information from IFC | LGPL-3.0-or-later |
| ifcbimtester | Wrapper for Gherkin based unit testing for IFC models | LGPL-3.0-or-later |
| ifcblender | Historic Blender IFC import add-on | LGPL-3.0-or-later\* |
| ifccityjson | Convert CityJSON to IFC | LGPL-3.0-or-later |
| ifcclash | Clash detection library and CLI app | LGPL-3.0-or-later |
| ifccobie | Extract IFC data for COBie handover requirements | LGPL-3.0-or-later |
| ifcconvert | CLI app to convert IFC to many other formats | LGPL-3.0-or-later\* |
| ifccsv | Library and CLI app to export and import schedules from IFC | LGPL-3.0-or-later |
| ifcdiff | Compare changes between IFC models | LGPL-3.0-or-later |
| ifcfm | Extract IFC data for FM handover requirements | LGPL-3.0-or-later |
| ifcgeom | Internal library for IfcOpenShell | LGPL-3.0-or-later\* |
| ifcgeom\_schema\_agnostic | Internal library for IfcOpenShell | LGPL-3.0-or-later\* |
| ifcgeomserver | Internal library for IfcOpenShell | LGPL-3.0-or-later\* |
| ifcjni | Internal library for IfcOpenShell | LGPL-3.0-or-later\* |
| ifcmax | Historic extension for IFC support in 3DS Max | LGPL-3.0-or-later\* |
| ifcopenshell-python | Python library for IFC manipulation | LGPL-3.0-or-later\* |
| ifcparse | Internal library for IfcOpenShell | LGPL-3.0-or-later\* |
| ifcpatch | Utility to run pre-packaged scripts to manipulate IFCs | LGPL-3.0-or-later |
| ifcsverchok | Blender Add-on for visual node programming with IFC | GPL-3.0-or-later |
| ifctester | Library, CLI and webapp for IDS model auditing | LGPL-3.0-or-later |
| ifcwrap | Internal library for IfcOpenShell | LGPL-3.0-or-later\* |
| qtviewer | Internal library for IfcOpenShell | LGPL-3.0-or-later\* |
| serializers | Internal library for IfcOpenShell | LGPL-3.0-or-later\* |
[LGPL]: https://github.com/IfcOpenShell/IfcOpenShell/tree/master/COPYING.LESSER "LGPL-3.0-or-later"
[IFC]: https://technical.buildingsmart.org/standards/ifc/ "IFC"
+4 -4
View File
@@ -3,14 +3,14 @@
<package xmlns="http://schemas.microsoft.com/packaging/2015/06/nuspec.xsd">
<metadata>
<id>blenderbim-nightly</id>
<version>blenderbim_build_version</version>
<version>blenderbim_build_version-alpha</version>
<packageSourceUrl>https://github.com/IfcOpenShell/IfcOpenShell</packageSourceUrl>
<owners>fbpyr</owners>
<!-- == SOFTWARE SPECIFIC SECTION == -->
<title>BlenderBIM install nightly</title>
<authors>Dion Moult and many more, see: https://github.com/IfcOpenShell/IfcOpenShell/graphs/contributors</authors>
<projectUrl>https://github.com/IfcOpenShell/IfcOpenShell</projectUrl>
<iconUrl>https://rawcdn.githack.com/fbpyr/IfcOpenShell/9662598ad4a7e4b6cd8980e430d07263835d0eda/choco/blenderbim/blenderbim.png</iconUrl>
<iconUrl>https://rawcdn.githack.com/IfcOpenShell/IfcOpenShell/c6ee1d1679d1298de0d7447ff108c22709c68e54/choco/blenderbim/blenderbim.png</iconUrl>
<!-- <copyright>Year Software Vendor</copyright> -->
<licenseUrl>https://github.com/IfcOpenShell/IfcOpenShell/blob/v0.7.0/COPYING</licenseUrl>
<requireLicenseAcceptance>true</requireLicenseAcceptance>
@@ -18,9 +18,9 @@
<docsUrl>https://blenderbim.org/docs/</docsUrl>
<!--<mailingListUrl></mailingListUrl>-->
<bugTrackerUrl>https://github.com/IfcOpenShell/IfcOpenShell/issues</bugTrackerUrl>
<tags>blender bim blenderbim python opensource foss</tags>
<tags>blender bim blenderbim ifc python opensource foss</tags>
<summary>opensource bim</summary>
<description>opensource bim addon for blender</description>
<description>opensource bim addon for blender - source code at: https://github.com/IfcOpenShell/IfcOpenShell</description>
<!-- <releaseNotes>__REPLACE_OR_REMOVE__MarkDown_Okay</releaseNotes> -->
<dependencies>
+4 -3
View File
@@ -15,10 +15,11 @@ def request_repo_info(url: str):
if sys.argv[1] == "--do_choco_release?":
now = datetime.datetime.now()
blenderbim_date = now.strftime("%y%m%d")
url = "https://github.com/IfcOpenShell/IfcOpenShell/releases/latest"
blenderbim_date = (now - datetime.timedelta(days=1)).strftime("%y%m%d")
url = "https://github.com/IfcOpenShell/IfcOpenShell/releases"
resp = request_repo_info(url)
if blenderbim_date in resp.url:
text = str(resp.read())
if blenderbim_date in text:
print("do_choco_release", end="")
+1 -1
View File
@@ -8,7 +8,7 @@ $appDataUserDir = [System.Environment]::GetEnvironmentVariable('appdata')
$unzipTargetDir = "$appDataUserDir\Blender Foundation\Blender\latest_blender_version_maj_min\scripts\addons"
$chocoBaseDir = [System.Environment]::GetEnvironmentVariable('ChocolateyInstall')
$addonEnable = "$chocoBaseDir\lib\blenderbim\tools\enable_blenderbim_addon.py"
$addonEnable = "$chocoBaseDir\lib\blenderbim-nightly\tools\enable_blenderbim_addon.py"
$programFilesDir = [System.Environment]::GetEnvironmentVariable('ProgramFiles')
$blenderExePath = "$env:ProgramFiles\Blender Foundation\Blender latest_blender_version_maj_min\blender.exe"
@@ -2,7 +2,7 @@
$unzippedDir = "$appDataUserDir\Blender Foundation\Blender\latest_blender_version_maj_min\scripts\addons\blenderbim"
$chocoBaseDir = [System.Environment]::GetEnvironmentVariable('ChocolateyInstall')
$addonDisable = "$chocoBaseDir\lib\blenderbim\tools\disable_blenderbim_addon.py"
$addonDisable = "$chocoBaseDir\lib\blenderbim-nightly\tools\disable_blenderbim_addon.py"
$programFilesDir = [System.Environment]::GetEnvironmentVariable('ProgramFiles')
$blenderExePath = "$env:ProgramFiles\Blender Foundation\Blender latest_blender_version_maj_min\blender.exe"
+68 -17
View File
@@ -54,6 +54,7 @@ OPTION(USE_VLD "Use Visual Leak Detector for debugging memory leaks, MSVC-only."
OPTION(USE_MMAP "Adds a command line options to parse IFC files from memory mapped files using Boost.Iostreams" OFF)
OPTION(BUILD_SHARED_LIBS "Build IfcParse and IfcGeom as shared libs (SO/DLL)." OFF)
OPTION(MSVC_PARALLEL_BUILD "Multi-threaded compilation in Microsoft Visual Studio (/MP)" OFF)
OPTION(WASM_BUILD OFF)
if (${HAS_MAX})
OPTION(BUILD_IFCMAX "Build IfcMax, a 3ds Max plug-in, Windows-only." OFF)
endif()
@@ -149,6 +150,14 @@ UNIFY_ENVVARS_AND_CACHE(MPFR_INCLUDE_DIR)
UNIFY_ENVVARS_AND_CACHE(MPFR_LIBRARY_DIR)
endif()
if(WASM_BUILD)
# when using the nix/build-all.py build script we should not
# look into the sysroot for most of the dependencies but rather
# in the designated build/ folder created by the script.
set(CMAKE_FIND_ROOT_PATH_BACKUP "${CMAKE_FIND_ROOT_PATH}")
set(CMAKE_FIND_ROOT_PATH "")
endif()
if (NOT MINIMAL_BUILD AND GLTF_SUPPORT AND BUILD_CONVERT)
UNIFY_ENVVARS_AND_CACHE(JSON_INCLUDE_DIR)
FIND_FILE(json_hpp "json.hpp" ${JSON_INCLUDE_DIR}/nlohmann)
@@ -199,7 +208,14 @@ ELSE()
ENDIF()
ENDIF()
set(BOOST_COMPONENTS system program_options regex thread date_time)
if (WASM_BUILD)
set(BOOST_COMPONENTS)
else()
# @todo review this, shouldn't this be all possible header-only now?
# ... or rewritten using C++17 features?
set(BOOST_COMPONENTS system program_options regex thread date_time)
endif()
if(USE_MMAP)
if(MSVC)
# filesystem is necessary for the utf-16 wpath
@@ -210,6 +226,10 @@ if(USE_MMAP)
add_definitions(-DUSE_MMAP)
endif()
FIND_PACKAGE(Boost REQUIRED COMPONENTS ${BOOST_COMPONENTS})
MESSAGE(STATUS "Boost include files found in ${Boost_INCLUDE_DIRS}")
MESSAGE(STATUS "Boost libraries found in ${Boost_LIBRARY_DIRS}")
if (NOT MINIMAL_BUILD)
# libxml2 is required for IFCXML (optional) and SVGFILL (mandatory)
find_package(LibXml2 REQUIRED)
@@ -220,11 +240,6 @@ if (NOT MINIMAL_BUILD AND IFCXML_SUPPORT)
set(SWIG_DEFINES ${SWIG_DEFINES} -DWITH_IFCXML)
endif()
FIND_PACKAGE(Boost REQUIRED COMPONENTS ${BOOST_COMPONENTS})
MESSAGE(STATUS "Boost include files found in ${Boost_INCLUDE_DIRS}")
MESSAGE(STATUS "Boost libraries found in ${Boost_LIBRARY_DIRS}")
# Usage:
# set(SOME_LIRARIES foo bar)
# add_debug_variants(SOME_LIRARIES "${SOME_LIRARIES}" d)
@@ -343,10 +358,21 @@ if("${libTKernelExt}" STREQUAL ".a")
set(OCCT_STATIC ON)
endif()
if(WASM_BUILD)
set(CMAKE_FIND_ROOT_PATH "${CMAKE_FIND_ROOT_PATH_BACKUP}")
endif()
if(OCCT_STATIC)
find_package(Threads)
# OPENCASCADE_LIBRARIES repeated three times below in order to fix cyclic dependencies - use --start-group ... --end-group instead?
set(OPENCASCADE_LIBRARIES ${OPENCASCADE_LIBRARIES} ${OPENCASCADE_LIBRARIES} ${OPENCASCADE_LIBRARIES} ${OPENCASCADE_LIBRARIES} ${OPENCASCADE_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT})
if(WASM_BUILD)
set(OPENCASCADE_LIBRARIES ${OPENCASCADE_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT})
else()
# OPENCASCADE_LIBRARIES repeated N times below in order to fix cyclic dependencies - use --start-group ... --end-group instead?
# tfk: --start-group ... --end-group didn't work on the apple linker when last tested
set(OPENCASCADE_LIBRARIES ${OPENCASCADE_LIBRARIES} ${OPENCASCADE_LIBRARIES} ${OPENCASCADE_LIBRARIES} ${OPENCASCADE_LIBRARIES} ${OPENCASCADE_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT})
endif()
if (NOT APPLE AND NOT WIN32)
set(OPENCASCADE_LIBRARIES ${OPENCASCADE_LIBRARIES} "rt")
endif()
@@ -620,7 +646,12 @@ function(files_for_ifc_version IFC_VERSION RESULT_NAME)
endfunction()
if(NOT SCHEMA_VERSIONS)
set(SCHEMA_VERSIONS "2x3" "4" "4x1" "4x2" "4x3_rc1" "4x3_rc2" "4x3_rc3" "4x3_rc4" "4x3")
if(WASM_BUILD)
# super arbitrarily try to keep size down at least a little bit
set(SCHEMA_VERSIONS "2x3" "4")
else()
set(SCHEMA_VERSIONS "2x3" "4" "4x1" "4x2" "4x3_rc1" "4x3_rc2" "4x3_rc3" "4x3_rc4" "4x3" "4x3_tc1" "4x3_add1")
endif()
endif()
foreach(s ${SCHEMA_VERSIONS})
@@ -697,7 +728,11 @@ if (BUILD_IFCGEOM)
foreach(s ${SCHEMA_VERSIONS})
set(IFCGEOM_SCHEMA_LIBRARIES ${IFCGEOM_SCHEMA_LIBRARIES} IfcGeom_ifc${s})
endforeach()
set(IFCOPENSHELL_LIBRARIES ${IFCOPENSHELL_LIBRARIES} IfcGeom ${IFCGEOM_SCHEMA_LIBRARIES} IfcGeom ${IFCGEOM_SCHEMA_LIBRARIES})
if (WASM_BUILD)
set(IFCOPENSHELL_LIBRARIES ${IFCOPENSHELL_LIBRARIES} IfcGeom ${IFCGEOM_SCHEMA_LIBRARIES})
else()
set(IFCOPENSHELL_LIBRARIES ${IFCOPENSHELL_LIBRARIES} IfcGeom ${IFCGEOM_SCHEMA_LIBRARIES} IfcGeom ${IFCGEOM_SCHEMA_LIBRARIES})
endif()
endif()
if (BUILD_CONVERT OR BUILD_IFCPYTHON)
foreach(s ${SCHEMA_VERSIONS})
@@ -741,7 +776,11 @@ set(IFCPARSE_FILES ${IFCPARSE_CPP_FILES} ${IFCPARSE_H_FILES})
add_library(IfcParse ${IFCPARSE_FILES})
set_target_properties(IfcParse PROPERTIES COMPILE_FLAGS -DIFC_PARSE_EXPORTS VERSION "0.6.0" SOVERSION "0.6")
TARGET_LINK_LIBRARIES(IfcParse ${Boost_LIBRARIES} ${BCRYPT_LIBRARIES} ${LIBXML2_LIBRARIES})
if (WASM_BUILD)
TARGET_LINK_LIBRARIES(IfcParse ${BCRYPT_LIBRARIES} ${LIBXML2_LIBRARIES})
else()
TARGET_LINK_LIBRARIES(IfcParse ${Boost_LIBRARIES} ${BCRYPT_LIBRARIES} ${LIBXML2_LIBRARIES})
endif()
if (BUILD_IFCGEOM)
@@ -753,7 +792,9 @@ set(IFCGEOM_FILES ${IFCGEOM_CPP_FILES} ${IFCGEOM_H_FILES})
foreach(s ${SCHEMA_VERSIONS})
add_library(IfcGeom_ifc${s} STATIC ${IFCGEOM_FILES})
set_target_properties(IfcGeom_ifc${s} PROPERTIES COMPILE_FLAGS "-DIFC_GEOM_EXPORTS -DIfcSchema=Ifc${s}")
TARGET_LINK_LIBRARIES(IfcGeom_ifc${s} IfcParse ${OPENCASCADE_LIBRARIES})
if (NOT WASM_BUILD)
TARGET_LINK_LIBRARIES(IfcGeom_ifc${s} IfcParse ${OPENCASCADE_LIBRARIES})
endif()
endforeach()
# IfcGeom (schema agnostic)
@@ -768,7 +809,11 @@ if (UNIX)
find_package(Threads)
endif()
TARGET_LINK_LIBRARIES(IfcGeom IfcParse ${IFCGEOM_SCHEMA_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT})
if (WASM_BUILD)
TARGET_LINK_LIBRARIES(IfcGeom ${IFCGEOM_SCHEMA_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT})
else()
TARGET_LINK_LIBRARIES(IfcGeom IfcParse ${IFCGEOM_SCHEMA_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT})
endif()
endif(BUILD_IFCGEOM)
@@ -785,7 +830,12 @@ set(SERIALIZERS_S_FILES ${SERIALIZERS_S_H_FILES} ${SERIALIZERS_S_CPP_FILES})
foreach(s ${SCHEMA_VERSIONS})
add_library(Serializers_ifc${s} STATIC ${SERIALIZERS_S_FILES})
set_target_properties(Serializers_ifc${s} PROPERTIES COMPILE_FLAGS "-DIFC_GEOM_EXPORTS -DIfcSchema=Ifc${s} ${CONVERT_PRECISION}")
TARGET_LINK_LIBRARIES(Serializers_ifc${s} IfcGeom ${OPENCASCADE_LIBRARIES} ${HDF5_LIBRARIES})
if (WASM_BUILD)
TARGET_LINK_LIBRARIES(Serializers_ifc${s} ${HDF5_LIBRARIES})
else()
TARGET_LINK_LIBRARIES(Serializers_ifc${s} IfcGeom ${OPENCASCADE_LIBRARIES} ${HDF5_LIBRARIES})
endif()
endforeach()
add_library(Serializers ${SERIALIZERS_FILES})
@@ -853,7 +903,8 @@ if (GIT_FOUND)
)
string(REPLACE "\n" ";" git_branch_list "${git_branches}")
foreach(git_branch_candidate IN ITEMS ${git_branch_list})
string(STRIP "${git_branch_candidate}" git_branch_candidate_2)
string(REPLACE "*" "" git_branch_candidate_temp "${git_branch_candidate}")
string(STRIP "${git_branch_candidate_temp}" git_branch_candidate_2)
if (NOT git_branch_candidate_2 MATCHES "^HEAD$")
set(git_branch ${git_branch_candidate_2})
endif()
@@ -961,12 +1012,12 @@ set(CPACK_SOURCE_GENERATOR "TGZ")
FOREACH(COMPONENT IN ITEMS ${BOOST_COMPONENTS})
string(REPLACE "_" "-" COMP ${COMPONENT})
set(BOOST_DEPS "${BOOST_DEPS}, libboost-${COMP}1.67.0")
set(BOOST_DEPS "${BOOST_DEPS}, libboost-${COMP}-dev")
endforeach(COMPONENT)
set(CPACK_DEBIAN_PACKAGE_NAME "${PROJECT_NAME}")
set(CPACK_DEBIAN_PACKAGE_MAINTAINER "${CPACK_PACKAGE_CONTACT}")
set(CPACK_DEBIAN_PACKAGE_DEPENDS "python3, libxml2, liboce-foundation11, liboce-modeling11, liboce-ocaf11, liboce-visualization11, liboce-ocaf-lite11, libpython3.8 ${BOOST_DEPS}")
set(CPACK_DEBIAN_PACKAGE_DEPENDS "python3, libxml2, libocct-foundation-dev, libocct-modeling-algorithms-dev, libocct-modeling-data-dev, libocct-ocaf-dev, libocct-visualization-dev, libocct-data-exchange-dev, libhdf5-serial-dev, libpython3.8 ${BOOST_DEPS}")
set(CPACK_DEBIAN_PACKAGE_DESCRIPTION_SUMMARY "${CPACK_PACKAGE_DESCRIPTION_SUMMARY}")
set(CPACK_DEBIAN_PACKAGE_DESCRIPTION "${CPACK_PACKAGE_DESCRIPTION}")
set(CPACK_DEBIAN_PACKAGE_PRIORITY "optional")
+3 -6
View File
@@ -1,11 +1,8 @@
compile:
conda activate conda-build && conda-build -c conda-forge . --keep-old-work --python 3.10.4
conda activate conda-build && conda-build -c conda-forge . --keep-old-work --python 3.10.5
debug:
conda activate conda-build && conda-debug -c conda-forge . --python 3.10.4
install:
conda env create -f environment.yml
update:
conda env update -n conda-build --file environment.yml --prune
env:
conda env update --f environment.yml --prune
+7 -5
View File
@@ -11,6 +11,7 @@ cmake -G "Ninja" ^
-D OCC_INCLUDE_DIR:FILEPATH="%LIBRARY_PREFIX%\include\opencascade" ^
-D OCC_LIBRARY_DIR:FILEPATH="%LIBRARY_PREFIX%\lib" ^
-D CGAL_INCLUDE_DIR:FILEPATH="%LIBRARY_PREFIX%\include" ^
-D GMP_INCLUDE_DIR:FILEPATH="%LIBRARY_PREFIX%\include" ^
-D GMP_LIBRARY_DIR:FILEPATH="%LIBRARY_PREFIX%\lib" ^
-D MPFR_LIBRARY_DIR:FILEPATH="%LIBRARY_PREFIX%\lib" ^
-D COLLADA_SUPPORT=OFF ^
@@ -26,14 +27,15 @@ cmake -G "Ninja" ^
-D COLLADA_SUPPORT:BOOL=OFF ^
-D BUILD_EXAMPLES:BOOL=OFF ^
-D BUILD_GEOMSERVER:BOOL=OFF ^
-D GLTF_SUPPORT:BOOL=OFF ^
-D GLTF_SUPPORT:BOOL=ON ^
-D BUILD_CONVERT:BOOL=ON ^
-D BUILD_IFCMAX:BOOL=OFF ^
-D IFCXML_SUPPORT:BOOL=OFF ^
-D BOOST_LIBRARYDIR:FILEPATH="%LIBRARY_PREFIX%\lib" ^
-D BOOST_INCLUDEDIR:FILEPATH="%LIBRARY_PREFIX%\include" ^
-D BOOST_USE_STATIC_LIBS:BOOL=OFF ^
-D IFCXML_SUPPORT:BOOL=ON ^
-D Boost_LIBRARYDIR:FILEPATH="%LIBRARY_PREFIX%\lib" ^
-D Boost_INCLUDEDIR:FILEPATH="%LIBRARY_PREFIX%\include" ^
-D Boost_USE_STATIC_LIBS:BOOL=OFF ^
%SRC_DIR%/cmake
if errorlevel 1 exit 1
:: Build and install
+3 -3
View File
@@ -18,8 +18,7 @@ cmake -G Ninja \
${CMAKE_PLATFORM_FLAGS[@]} \
-DCMAKE_PREFIX_PATH=$PREFIX \
-DCMAKE_SYSTEM_PREFIX_PATH=$PREFIX \
-DPython3_FIND_STRATEGY=LOCATION \
-DPython3_FIND_FRAMEWORK=NEVER \
-DPYTHON_EXECUTABLE:FILEPATH=$PYTHON \
-DGMP_LIBRARY_DIR=$PREFIX/lib \
-DMPFR_LIBRARY_DIR=$PREFIX/lib \
-DOCC_INCLUDE_DIR=$PREFIX/include/opencascade \
@@ -31,7 +30,8 @@ cmake -G Ninja \
-DCGAL_INCLUDE_DIR=$PREFIX/include \
-DCOLLADA_SUPPORT=0 \
-DBUILD_EXAMPLES:BOOL=OFF \
-DIFCXML_SUPPORT:BOOL=OFF \
-DIFCXML_SUPPORT:BOOL=ON \
-DGLTF_SUPPORT:BOOL=ON \
-DBUILD_CONVERT:BOOL=ON \
-DBUILD_IFCPYTHON:BOOL=ON \
-DBUILD_IFCGEOM:BOOL=ON \
+17 -31
View File
@@ -13,6 +13,7 @@ source:
build:
binary_relocation: false [osx]
number: 1
requirements:
build:
@@ -24,46 +25,31 @@ requirements:
host:
- python
- occt >={{ occt_version }}
- hdf5 >={{ hdf5_version }}
- cgal-cpp >={{ cgal_cpp_version }}
- nlohmann_json
- boost-cpp
- occt
- libxml2
- cgal-cpp
- hdf5
- mpfr
- gmp # [unix]
- mpir # [win]
- nlohmann_json
run:
- python
- occt >={{ occt_version }}
- cgal-cpp >={{ cgal_cpp_version }}
- hdf5 >={{ hdf5_version }}
- lark-parser
- deepdiff
- requests
- boost-cpp
- occt
- libxml2
- cgal-cpp
- hdf5
- mpfr
- gmp # [unix]
- mpir # [win]
- nlohmann_json
- isodate
- numpy >=1.17
test:
imports:
- ifcopenshell
requires:
- pytest
- pytest-cov
- numpy
- lxml
- isodate
- lark
- networkx
- xmlschema
#source_files:
# - src/ifcopenshell-python/test/api
# - src/ifcopenshell-python/test/util
# - src/ifcopenshell-python/test/bootstrap.py
# - src/ifcopenshell-python/test/test_file.py
# - src/ifcopenshell-python/test/test_wall_opening.py
# - src/ifcopenshell-python/test/__init__.py
#commands:
# #- pip install bcf-client
# - cd ../src/ifcopenshell-python/test && pytest -p no:pytest-blender
about:
home: http://ifcopenshell.org
+186 -78
View File
@@ -23,10 +23,10 @@
# This script builds IfcOpenShell and its dependencies #
# #
# Prerequisites for this script to function correctly: #
# * cmake * git * bzip2 * tar * c(++) compilers * yacc * autoconf #
# * cmake * git * bzip2 * tar * c(++) compilers * autoconf #
# #
# if building with USE_OCCT additionally: #
# * freetype * glx.h #
# * glx.h #
# #
# if building with OCCT 7.4.0 additionally: #
# * libfontconfig1-dev #
@@ -39,14 +39,14 @@
# #
# on debian 7.8 these can be obtained with: #
# $ apt-get install git gcc g++ autoconf bison bzip2 cmake #
# libfreetype6-dev mesa-common-dev libffi-dev libfontconfig1-dev #
# mesa-common-dev libffi-dev libfontconfig1-dev #
# #
# on ubuntu 14.04: #
# $ apt-get install git gcc g++ autoconf bison make cmake #
# libfreetype6-dev mesa-common-dev libffi-dev libfontconfig1-dev #
# mesa-common-dev libffi-dev libfontconfig1-dev #
# #
# on OS X El Capitan with homebrew: #
# $ brew install git bison autoconf automake freetype libffi cmake #
# $ brew install git bison autoconf automake libffi cmake #
# #
###############################################################################
import logging
@@ -72,19 +72,19 @@ PROJECT_NAME = "IfcOpenShell"
USE_CURRENT_PYTHON_VERSION = os.getenv("USE_CURRENT_PYTHON_VERSION")
ADD_COMMIT_SHA = os.getenv("ADD_COMMIT_SHA")
PYTHON_VERSIONS = ["3.6.14", "3.7.12", "3.8.12", "3.9.7", "3.10.0"]
PYTHON_VERSIONS = ["3.6.14", "3.7.13", "3.8.13", "3.9.11", "3.10.3", "3.11.0"]
JSON_VERSION = "v3.6.1"
OCE_VERSION = "0.18.3"
OCCT_VERSION = "7.5.3"
BOOST_VERSION = "1.71.0"
BOOST_VERSION = "1.80.0"
PCRE_VERSION = "8.41"
LIBXML2_VERSION = "2.9.11"
SWIG_VERSION = "4.0.2"
OPENCOLLADA_VERSION = "v1.6.68"
HDF5_VERSION = "1.12.1"
GMP_VERSION = "6.1.2"
MPFR_VERSION = "3.1.5" # latest is 4.1.0
GMP_VERSION = "6.2.1"
MPFR_VERSION = "3.1.6" # latest is 4.1.0
CGAL_VERSION = "5.3"
# binaries
@@ -97,13 +97,15 @@ cc = "cc"
cplusplus = "c++"
autoconf = "autoconf"
automake = "automake"
yacc = "yacc"
make = "make"
date = "date"
curl = "curl"
wget = "wget"
strip = "strip"
explicit_targets = [s for s in sys.argv[1:] if not s.startswith("-")]
flags = set(s.lstrip('-') for s in sys.argv[1:] if s.startswith("-"))
# Helper function for coloured printing
NO_COLOR = "\033[0m" # <ref>http://stackoverflow.com/questions/5947742/how-to-change-the-output-color-of-echo-in-linux</ref>
@@ -136,9 +138,11 @@ if platform.system() == "Darwin":
IFCOS_NUM_BUILD_PROCS = os.getenv("IFCOS_NUM_BUILD_PROCS", multiprocessing.cpu_count() + 1)
CMAKE_DIR = os.path.realpath(os.path.join("..", "cmake"))
CMAKE_DIR = os.path.realpath(os.path.join(os.path.dirname(__file__), "..", "cmake"))
path = ["..", "build", platform.system(), platform.machine()]
build_dir = os.environ.get("BUILD_DIR", os.path.join(os.path.dirname(__file__), "..", "build"))
path = [build_dir, platform.system(), "wasm" if "wasm" in flags else platform.machine()]
if TOOLSET:
path.append(TOOLSET)
DEFAULT_DEPS_DIR = os.path.realpath(os.path.join(*path))
@@ -187,11 +191,12 @@ dependency_tree = {
'boost': (),
'libxml2': (),
'python': (),
'occ': (),
'occ': ('freetype',),
'pcre': (),
'json': (),
'hdf5': (),
'cgal': ()
'cgal': (),
'freetype': (),
}
def v(dep):
@@ -200,10 +205,12 @@ def v(dep):
for x in v(d):
yield x
tgts = [s for s in sys.argv[1:] if not s.startswith("-")]
flags = set(s for s in sys.argv[1:] if s.startswith("-"))
if "v" in flags:
logger.setLevel(logging.DEBUG)
else:
logger.setLevel(logging.INFO)
BUILD_STATIC = not "-shared" in flags
BUILD_STATIC = "shared" not in flags
ENABLE_FLAG = "--enable-static" if BUILD_STATIC else "--enable-shared"
DISABLE_FLAG = "--disable-shared" if BUILD_STATIC else "--disable-static"
LINK_TYPE = "static" if BUILD_STATIC else "shared"
@@ -211,16 +218,21 @@ LINK_TYPE_UCFIRST = LINK_TYPE[0].upper() + LINK_TYPE[1:]
LIBRARY_EXT = "a" if BUILD_STATIC else "so"
PIC = "-fPIC" if BUILD_STATIC else ""
if len(tgts):
targets = set(sum((list(v(target)) for target in tgts), []))
if any(f.startswith("py-") for f in flags):
PYTHON_VERSIONS = [pyv for pyv in PYTHON_VERSIONS if "py-%s" % "".join(pyv.split('.')[0:2]) in flags]
if len(explicit_targets):
targets = set(sum((list(v(target)) for target in explicit_targets), []))
else:
targets = set(dependency_tree.keys())
targets = set(t for t in targets if 'without-%s' % t.lower() not in flags)
print("Building:", *sorted(targets, key=lambda t: len(list(v(t)))))
# Check that required tools are in PATH
for cmd in [git, bunzip2, tar, cc, cplusplus, autoconf, automake, yacc, make, "patch", "cmake"]:
for cmd in [git, bunzip2, tar, cc, cplusplus, autoconf, automake, make, "patch", "cmake"]:
if which(cmd) is None:
raise ValueError(f"Required tool '{cmd}' not installed or not added to PATH")
@@ -238,7 +250,7 @@ if not os.path.exists(LOG_FILE):
open(LOG_FILE, "w").close()
logger.info(f"using command log file '{LOG_FILE}'")
def run(cmds, cwd=None):
def run(cmds, cwd=None, can_fail=False):
"""
Wraps `subprocess.Popen.communicate()` and logs the command being executed,
@@ -255,7 +267,7 @@ def run(cmds, cwd=None):
log_file_handle.close()
logger.debug(f"command returned {proc.returncode}")
if proc.returncode != 0:
if proc.returncode != 0 and not can_fail:
print("-" * 70)
print(stderr)
print("-" * 70)
@@ -263,6 +275,14 @@ def run(cmds, cwd=None):
return stdout.strip()
if platform.system() == "Darwin":
if run(["sw_vers", "-productVersion"]) >= "11.":
# Apparently not supported
PYTHON_VERSIONS = [pv for pv in PYTHON_VERSIONS if tuple(map(int, pv.split("."))) >= (3, 7)]
if run(["sw_vers", "-productVersion"]) < "10.16":
# Apparently not supported
PYTHON_VERSIONS = [pv for pv in PYTHON_VERSIONS if tuple(map(int, pv.split("."))) < (3, 11)]
BOOST_VERSION_UNDERSCORE = BOOST_VERSION.replace(".", "_")
OCE_LOCATION = f"https://github.com/tpaviot/oce/archive/OCE-{OCE_VERSION}.tar.gz"
@@ -277,7 +297,12 @@ def run_autoconf(arg1, configure_args, cwd):
run([bash, "./autogen.sh"], cwd=os.path.realpath(os.path.join(cwd, ".."))) # only run autogen.sh in the directory it is located and use cwd to achieve that in order to not mess up things
# Using `sh` over `bash` fixes issues with building swig
prefix = os.path.realpath(f"{DEPS_DIR}/install/{arg1}")
run(["/bin/sh", "../configure"] + configure_args + [f"--prefix={prefix}"], cwd=cwd)
wasm = []
if "wasm" in flags:
wasm.append("emconfigure")
run([*wasm, "/bin/sh", "../configure"] + configure_args + [f"--prefix={prefix}"], cwd=cwd)
def run_cmake(arg1, cmake_args, cmake_dir=None, cwd=None):
@@ -285,7 +310,12 @@ def run_cmake(arg1, cmake_args, cmake_dir=None, cwd=None):
P = ".."
else:
P = cmake_dir
run(["cmake", P] + cmake_args + [f"-DCMAKE_BUILD_TYPE={BUILD_CFG}"], cwd=cwd)
wasm = []
if "wasm" in flags:
wasm.append("emcmake")
run([*wasm, "cmake", P, *cmake_args, f"-DCMAKE_BUILD_TYPE={BUILD_CFG}"], cwd=cwd)
def git_clone_or_pull_repository(clone_url, target_dir, revision=None):
@@ -367,12 +397,15 @@ def build_dependency(name, mode, build_tool_args, download_url, download_name, d
urlretrieve(url, os.path.join(extract_dir, path))
if patch is not None:
patch_abs = os.path.abspath(os.path.join(os.path.dirname(__file__), patch))
if os.path.exists(patch_abs):
try: run(["patch", "-p1", "--batch", "--forward", "-i", patch_abs], cwd=extract_dir)
except Exception as e:
# Assert that the patch has already been applied
run(["patch", "-p1", "--batch", "--reverse", "--dry-run", "-i", patch_abs], cwd=extract_dir)
if isinstance(patch, str):
patch = [patch]
for p in patch:
patch_abs = os.path.abspath(os.path.join(os.path.dirname(__file__), p))
if os.path.exists(patch_abs):
try: run(["patch", "-p1", "--batch", "--forward", "-i", patch_abs], cwd=extract_dir)
except Exception as e:
# Assert that the patch has already been applied
run(["patch", "-p1", "--batch", "--reverse", "--dry-run", "-i", patch_abs], cwd=extract_dir)
if mode == "ctest":
run(["ctest", "-S", "HDF5config.cmake,BUILD_GENERATOR=Unix", "-C", BUILD_CFG, "-V", "-O", "hdf5.log"], cwd=extract_dir)
@@ -403,7 +436,7 @@ def build_dependency(name, mode, build_tool_args, download_url, download_name, d
logger.info(f"\rConfiguring {name}...")
run([bash, "./bootstrap.sh"], cwd=extract_dir)
logger.info(f"\rBuilding {name}... ")
run(["./b2", f"-j{IFCOS_NUM_BUILD_PROCS}"] + build_tool_args, cwd=extract_dir)
run(["./b2", f"-j{IFCOS_NUM_BUILD_PROCS}"] + build_tool_args, cwd=extract_dir, can_fail="wasm" in flags)
logger.info(f"\rInstalling {name}... ")
shutil.copytree(os.path.join(extract_dir, "boost"), os.path.join(DEPS_DIR, "install", f"boost-{BOOST_VERSION}", "boost"))
logger.info(f"\rInstalled {name} \n")
@@ -414,11 +447,13 @@ cecho("Collecting dependencies:", GREEN)
# TODO: This is untested
ADDITIONAL_ARGS = []
BOOST_ADDRESS_MODEL = []
if platform.system() == "Darwin":
ADDITIONAL_ARGS = [f"-mmacosx-version-min={TOOLSET}"] + ADDITIONAL_ARGS
if "wasm" in flags:
ADDITIONAL_ARGS.extend(("-sWASM_BIGINT", "-fexceptions"))
# If the linker supports GC sections, set it up to reduce binary file size
# -fPIC is required for the shared libraries to work
@@ -427,7 +462,7 @@ CFLAGS = os.environ.get("CFLAGS", "")
LDFLAGS = os.environ.get("LDFLAGS", "")
ADDITIONAL_ARGS_STR = " ".join(ADDITIONAL_ARGS)
if sp.call([bash, "-c", "ld --gc-sections 2>&1 | grep -- --gc-sections &> /dev/null"]) != 0:
if "wasm" not in flags and sp.call([bash, "-c", "ld --gc-sections 2>&1 | grep -- --gc-sections &> /dev/null"]) != 0:
CXXFLAGS_MINIMAL = f"{CXXFLAGS} {PIC} {ADDITIONAL_ARGS_STR}"
CFLAGS_MINIMAL = f"{CFLAGS} {PIC} {ADDITIONAL_ARGS_STR}"
if BUILD_STATIC:
@@ -497,8 +532,27 @@ if "swig" in targets:
download_tool=download_tool_git,
revision=f"rel-{SWIG_VERSION}"
)
if "freetype" in targets:
build_dependency(
name=f"freetype",
mode="cmake",
build_tool_args=[
f"-DCMAKE_INSTALL_PREFIX={DEPS_DIR}/install/freetype"
],
download_url = "https://github.com/freetype/freetype",
download_name = "freetype2",
download_tool=download_tool_git,
)
if USE_OCCT and "occ" in targets:
patches = []
if OCCT_VERSION < "7.4":
patches.append("./patches/occt/enable-exception-handling.patch")
if "wasm" in flags:
patches.append("./patches/occt/no_em_js.patch")
build_dependency(
name=f"occt-{OCCT_VERSION}",
mode="cmake",
@@ -506,12 +560,13 @@ if USE_OCCT and "occ" in targets:
f"-DINSTALL_DIR={DEPS_DIR}/install/occt-{OCCT_VERSION}",
f"-DBUILD_LIBRARY_TYPE={LINK_TYPE_UCFIRST}",
"-DBUILD_MODULE_Draw=0",
"-DBUILD_RELEASE_DISABLE_EXCEPTIONS=Off"
"-DBUILD_RELEASE_DISABLE_EXCEPTIONS=Off",
f"-D3RDPARTY_FREETYPE_DIR={DEPS_DIR}/install/freetype"
],
download_url = "https://github.com/Open-Cascade-SAS/OCCT",
download_name = "occt",
download_tool=download_tool_git,
patch=None if OCCT_VERSION >= "7.4" else "./patches/occt/enable-exception-handling.patch",
patch=patches,
revision="V" + OCCT_VERSION.replace('.', '_')
)
elif "occ" in targets:
@@ -548,6 +603,15 @@ if "libxml2" in targets:
)
if "OpenCOLLADA" in targets:
patches = ["./patches/opencollada/pr622_and_disable_subdirs.patch"]
if "wasm" in flags:
# This is necessary for the WASM build, because recent versions of
# clang don't have the tr1:: namespace anymore. However, it breaks
# some versions of gcc (9.4.0 at least) due to specializing std::hash
# outside of the std:: namespace.
patches.append("./patches/opencollada/remove_tr1.patch")
build_dependency(
"OpenCOLLADA",
"cmake",
@@ -562,11 +626,11 @@ if "OpenCOLLADA" in targets:
download_url="https://github.com/KhronosGroup/OpenCOLLADA.git",
download_name="OpenCOLLADA",
download_tool=download_tool_git,
patch="./patches/opencollada/pr622_and_disable_subdirs.patch",
patch=patches,
revision=OPENCOLLADA_VERSION
)
if "python" in targets and not USE_CURRENT_PYTHON_VERSION:
if "python" in targets and not USE_CURRENT_PYTHON_VERSION and "wasm" not in flags:
# Python should not be built with -fvisibility=hidden, from experience that introduces segfaults
OLD_CXX_FLAGS = os.environ["CXXFLAGS"]
OLD_C_FLAGS = os.environ["CFLAGS"]
@@ -577,7 +641,7 @@ if "python" in targets and not USE_CURRENT_PYTHON_VERSION:
# with the system python because of some threading initialization
PYTHON_CONFIGURE_ARGS = []
if platform.system() == "Darwin":
PYTHON_CONFIGURE_ARGS = ["--disable-static", "--enable-shared"]
PYTHON_CONFIGURE_ARGS = ["--enable-shared"]
for PYTHON_VERSION in PYTHON_VERSIONS:
try:
@@ -605,6 +669,9 @@ if "python" in targets and not USE_CURRENT_PYTHON_VERSION:
if "boost" in targets:
str_concat = lambda prefix: lambda postfix: "" if postfix.strip() == "" else "=".join((prefix, postfix.strip()))
toolset = []
if "wasm" in flags:
toolset.append("toolset=emscripten")
build_dependency(
f"boost-{BOOST_VERSION}",
mode="bjam",
@@ -616,21 +683,30 @@ if "boost" in targets:
"--with-thread",
"--with-date_time",
"--with-iostreams",
f"link={LINK_TYPE}"
] + \
BOOST_ADDRESS_MODEL + \
list(map(str_concat("cxxflags"), CXXFLAGS.strip().split(' '))) + \
list(map(str_concat("linkflags"), LDFLAGS.strip().split(' '))) + \
["stage", "-s", "NO_BZIP2=1"],
f"link={LINK_TYPE}",
*toolset,
*map(str_concat("cxxflags"), CXXFLAGS.strip().split(' ')),
*map(str_concat("linkflags"), LDFLAGS.strip().split(' ')),
"stage", "-s", "NO_BZIP2=1"],
download_url=BOOST_LOCATION,
patch="./patches/boost/boostorg_regex_62.patch",
download_name=f"boost_{BOOST_VERSION_UNDERSCORE}.tar.bz2"
)
if "wasm" in flags:
# only supported on nix for now
run(("find", ".", "-name", "*.bc", "-exec", "bash", "-c", "emar q ${1%.bc}.a $1", "bash", "{}", ";"), cwd=f"{DEPS_DIR}/install/boost-{BOOST_VERSION}/lib")
if "cgal" in targets:
gmp_args = []
mpfr_args = []
if "wasm" in flags:
gmp_args.extend(("--disable-assembly", "--host", "none", "--enable-cxx"))
mpfr_args.extend(("--host", "none"))
build_dependency(
name=f"gmp-{GMP_VERSION}",
mode="autoconf",
build_tool_args=["--disable-shared", "--with-pic"],
build_tool_args=[ENABLE_FLAG, DISABLE_FLAG, "--with-pic", *gmp_args],
download_url="https://ftp.gnu.org/gnu/gmp/",
download_name=f"gmp-{GMP_VERSION}.tar.bz2"
)
@@ -638,7 +714,7 @@ if "cgal" in targets:
build_dependency(
name=f"mpfr-{MPFR_VERSION}",
mode="autoconf",
build_tool_args=["--disable-shared", f"--with-gmp={DEPS_DIR}/install/gmp-{GMP_VERSION}"],
build_tool_args=[ENABLE_FLAG, DISABLE_FLAG, *mpfr_args, f"--with-gmp={DEPS_DIR}/install/gmp-{GMP_VERSION}"],
download_url=f"http://www.mpfr.org/mpfr-{MPFR_VERSION}/",
download_name=f"mpfr-{MPFR_VERSION}.tar.bz2"
)
@@ -647,9 +723,9 @@ if "cgal" in targets:
name=f"cgal-{CGAL_VERSION}",
mode="cmake",
build_tool_args=[
f"-DGMP_LIBRARIES={DEPS_DIR}/install/gmp-{GMP_VERSION}/lib/libgmp.a",
f"-DGMP_LIBRARIES={DEPS_DIR}/install/gmp-{GMP_VERSION}/lib/libgmp.{LIBRARY_EXT}",
f"-DGMP_INCLUDE_DIR={DEPS_DIR}/install/gmp-{GMP_VERSION}/include",
f"-DMPFR_LIBRARIES={DEPS_DIR}/install/mpfr-{MPFR_VERSION}/lib/libmpfr.a" ,
f"-DMPFR_LIBRARIES={DEPS_DIR}/install/mpfr-{MPFR_VERSION}/lib/libmpfr.{LIBRARY_EXT}" ,
f"-DMPFR_INCLUDE_DIR={DEPS_DIR}/install/mpfr-{MPFR_VERSION}/include",
f"-DBoost_INCLUDE_DIR={DEPS_DIR}/install/boost-{BOOST_VERSION}",
f"-DCMAKE_INSTALL_PREFIX={DEPS_DIR}/install/cgal-{CGAL_VERSION}/",
@@ -672,18 +748,8 @@ os.makedirs(IFCOS_DIR, exist_ok=True)
executables_dir = os.path.join(IFCOS_DIR, "executables")
os.makedirs(executables_dir, exist_ok=True)
logger.info("\rConfiguring executables...")
OFF_ON = ["OFF", "ON"]
exec_args = [
"-DBUILD_IFCGEOM=" +OFF_ON["IfcGeom" in targets],
"-DBUILD_GEOMSERVER=" +OFF_ON["IfcGeomServer" in targets],
"-DBUILD_CONVERT=" +OFF_ON["IfcConvert" in targets],
"-DBUILD_IFCPYTHON=" "OFF",
"-DCMAKE_INSTALL_PREFIX=" f"{DEPS_DIR}/install/ifcopenshell",
]
cmake_args = [
"-DUSE_MMAP=" "OFF",
"-DBUILD_EXAMPLES=" "OFF",
@@ -695,6 +761,11 @@ cmake_args = [
"-DADD_COMMIT_SHA=" +("On" if ADD_COMMIT_SHA else "Off")
]
if "wasm" in flags:
# Boost is built by the build script so should not be found
# inside of the sysroot set by the emscriptem toolchain
cmake_args.append("-DWASM_BUILD=On")
if "cgal" in targets:
cmake_args.extend([
"-DCGAL_INCLUDE_DIR=" f"{DEPS_DIR}/install/cgal-{CGAL_VERSION}/include",
@@ -746,20 +817,36 @@ if "hdf5" in targets:
"-DHDF5_INCLUDE_DIR=" f"{DEPS_DIR}/install/hdf5-{HDF5_VERSION}/include",
"-DHDF5_LIBRARY_DIR=" f"{DEPS_DIR}/install/hdf5-{HDF5_VERSION}/lib"
])
else:
cmake_args.append("-DHDF5_SUPPORT=Off")
run_cmake("", exec_args + cmake_args, cmake_dir=CMAKE_DIR, cwd=executables_dir)
if not explicit_targets or {"IfcGeom", "IfcConvert", "IfcGeomServer"} & set(explicit_targets):
logger.info("\rConfiguring executables...")
logger.info("\rBuilding executables... ")
exec_args = [
"-DBUILD_IFCGEOM=" +OFF_ON["IfcGeom" in targets],
"-DBUILD_GEOMSERVER=" +OFF_ON["IfcGeomServer" in targets],
"-DBUILD_CONVERT=" +OFF_ON["IfcConvert" in targets],
"-DBUILD_IFCPYTHON=" "OFF",
"-DCMAKE_INSTALL_PREFIX=" f"{DEPS_DIR}/install/ifcopenshell",
]
run_cmake("", exec_args + cmake_args, cmake_dir=CMAKE_DIR, cwd=executables_dir)
run([make, f"-j{IFCOS_NUM_BUILD_PROCS}"], cwd=executables_dir)
run([make, "install/strip" if BUILD_CFG == "Release" else "install"], cwd=executables_dir)
logger.info("\rBuilding executables... ")
run([make, f"-j{IFCOS_NUM_BUILD_PROCS}"], cwd=executables_dir)
run([make, "install/strip" if BUILD_CFG == "Release" else "install"], cwd=executables_dir)
if "IfcOpenShell-Python" in targets:
# On OSX the actual Python library is not linked against.
ADDITIONAL_ARGS = ""
if platform.system() == "Darwin":
ADDITIONAL_ARGS = "-Wl,-flat_namespace,-undefined,suppress"
if "wasm" in flags:
ADDITIONAL_ARGS = f"-Wl,-undefined,suppress -sSIDE_MODULE=2 -sEXPORTED_FUNCTIONS=_PyInit__ifcopenshell_wrapper"
os.environ["CXXFLAGS"] = f"{CXXFLAGS_MINIMAL} {ADDITIONAL_ARGS}"
os.environ["CFLAGS"] = f"{CFLAGS_MINIMAL} {ADDITIONAL_ARGS}"
os.environ["LDFLAGS"] = f"{LDFLAGS} {ADDITIONAL_ARGS}"
@@ -776,36 +863,57 @@ if "IfcOpenShell-Python" in targets:
os.environ["PYTHON_LIBRARY_BASENAME"] = os.path.basename(python_library)
swig_when_built = []
if "swig" in targets:
swig_when_built.append(f"-DSWIG_EXECUTABLE={DEPS_DIR}/install/swig/bin/swig")
run_cmake("",
cmake_args + [
"-DPYTHON_LIBRARY=" +python_library,
"-DPYTHON_EXECUTABLE=" +python_executable,
*([f"-DPYTHON_EXECUTABLE={python_executable}"] if python_executable else []),
# *([f"-DPYTHON_MODULE_INSTALL_DIR={os.environ['PYTHONPATH']}/ifcopenshell"] if "wasm" in flags else []),
*(["-DPYTHON_MODULE_INSTALL_DIR="+os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "package"))] if "wasm" in flags else []),
"-DPYTHON_INCLUDE_DIR=" +python_include,
"-DSWIG_EXECUTABLE=" f"{DEPS_DIR}/install/swig/bin/swig",
"-DCMAKE_INSTALL_PREFIX=" f"{DEPS_DIR}/install/ifcopenshell/tmp",
"-DUSERSPACE_PYTHON_PREFIX=" +["Off", "On"][os.environ.get("PYTHON_USER_SITE", "").lower() in {"1", "on", "true"}]
], cmake_dir=CMAKE_DIR, cwd=python_dir)
"-DUSERSPACE_PYTHON_PREFIX=" +["Off", "On"][os.environ.get("PYTHON_USER_SITE", "").lower() in {"1", "on", "true"}],
*swig_when_built],
cmake_dir=CMAKE_DIR, cwd=python_dir)
logger.info(f"\rBuilding python {python_version} wrapper... ")
run([make, f"-j{IFCOS_NUM_BUILD_PROCS}", "_ifcopenshell_wrapper"], cwd=python_dir)
run([make, "install/local"], cwd=os.path.join(python_dir, "ifcwrap"))
module_dir = os.path.dirname(run([python_executable, "-c", "import inspect, ifcopenshell; print(inspect.getfile(ifcopenshell))"]))
if python_executable:
module_dir = os.path.dirname(run([python_executable, "-c", "import inspect, ifcopenshell; print(inspect.getfile(ifcopenshell))"]))
if platform.system() != "Darwin":
# TODO: This symbol name depends on the Python version?
run([strip, "-s", "-K", "PyInit__ifcopenshell_wrapper", "_ifcopenshell_wrapper.so"], cwd=module_dir)
return module_dir
if platform.system() != "Darwin":
# TODO: This symbol name depends on the Python version?
run([strip, "-s", "-K", "PyInit__ifcopenshell_wrapper", "_ifcopenshell_wrapper.so"], cwd=module_dir)
return module_dir
if USE_CURRENT_PYTHON_VERSION:
python_info = sysconfig.get_paths()
python_lib = os.path.join(
sysconfig.get_config_var('LIBDIR'),
sysconfig.get_config_var('multiarchsubdir').replace("/", ""),
sysconfig.get_config_var("INSTSONAME")
if "wasm" in flags:
compile_python_wrapper(
f"{os.environ['PYMAJOR']}.{os.environ['PYMINOR']}.{os.environ['PYMICRO']}",
f"{os.environ['TARGETINSTALLDIR']}/lib/libpython{os.environ['PYMAJOR']}.{os.environ['PYMINOR']}.a",
os.environ['PYTHONINCLUDE'],
None
)
elif USE_CURRENT_PYTHON_VERSION:
python_info = sysconfig.get_paths()
py_path_components = [
sysconfig.get_config_var('LIBDIR'),
sysconfig.get_config_var("INSTSONAME")
]
if sysconfig.get_config_var('multiarchsubdir'):
py_path_components.insert(1, sysconfig.get_config_var('multiarchsubdir').replace("/", ""))
python_lib = os.path.join(*py_path_components)
compile_python_wrapper(platform.python_version(), python_lib, python_info["include"], sys.executable)
else:
for python_version in PYTHON_VERSIONS:
+20
View File
@@ -0,0 +1,20 @@
--- a/tools/build/src/tools/emscripten.jam
+++ b/tools/build/src/tools/emscripten.jam
@@ -6,6 +6,7 @@
import feature ;
import os ;
import toolset ;
+import generators ;
import common ;
import gcc ;
import type ;
@@ -52,6 +53,8 @@
<debug-symbols>off <debug-symbols>on
<rtti>off <rtti>on
;
+generators.override builtin.lib-generator : emscripten.prebuilt ;
+generators.override emscripten.searched-lib-generator : searched-lib-generator ;
type.set-generated-target-suffix EXE : <toolset>emscripten : "js" ;
type.set-generated-target-suffix OBJ : <toolset>emscripten : "bc" ;
+99
View File
@@ -0,0 +1,99 @@
diff --git a/src/Message/Message_PrinterSystemLog.cxx b/src/Message/Message_PrinterSystemLog.cxx
index 0c82c2167..a2e9e6d68 100644
--- a/src/Message/Message_PrinterSystemLog.cxx
+++ b/src/Message/Message_PrinterSystemLog.cxx
@@ -55,27 +55,6 @@
return ANDROID_LOG_DEBUG;
}
#elif defined(__EMSCRIPTEN__)
- #include <emscripten/emscripten.h>
-
- //! Print message to console.debug().
- EM_JS(void, occJSConsoleDebug, (const char* theStr), {
- console.debug(UTF8ToString(theStr));
- });
-
- //! Print message to console.info().
- EM_JS(void, occJSConsoleInfo, (const char* theStr), {
- console.info(UTF8ToString(theStr));
- });
-
- //! Print message to console.warn().
- EM_JS(void, occJSConsoleWarn, (const char* theStr), {
- console.warn(UTF8ToString(theStr));
- });
-
- //! Print message to console.error().
- EM_JS(void, occJSConsoleError, (const char* theStr), {
- console.error(UTF8ToString(theStr));
- });
#else
#include <syslog.h>
@@ -169,16 +148,6 @@ void Message_PrinterSystemLog::send (const TCollection_AsciiString& theString,
#elif defined(__ANDROID__)
__android_log_write (getAndroidLogPriority (theGravity), myEventSourceName.ToCString(), theString.ToCString());
#elif defined(__EMSCRIPTEN__)
- // don't use bogus emscripten_log() corrupting UNICODE strings
- switch (theGravity)
- {
- case Message_Trace: occJSConsoleDebug(theString.ToCString()); return;
- case Message_Info: occJSConsoleInfo (theString.ToCString()); return;
- case Message_Warning: occJSConsoleWarn (theString.ToCString()); return;
- case Message_Alarm: occJSConsoleError(theString.ToCString()); return;
- case Message_Fail: occJSConsoleError(theString.ToCString()); return;
- }
- occJSConsoleWarn (theString.ToCString());
#else
syslog (getSysLogPriority (theGravity), "%s", theString.ToCString());
#endif
diff --git a/src/OSD/OSD_MemInfo.cxx b/src/OSD/OSD_MemInfo.cxx
index 08a939beb..7c1fc79b3 100644
--- a/src/OSD/OSD_MemInfo.cxx
+++ b/src/OSD/OSD_MemInfo.cxx
@@ -37,15 +37,6 @@
#include <OSD_MemInfo.hxx>
-#if defined(__EMSCRIPTEN__)
- #include <emscripten.h>
-
- //! Return WebAssembly heap size in bytes.
- EM_JS(size_t, OSD_MemInfo_getModuleHeapLength, (), {
- return Module.HEAP8.length;
- });
-#endif
-
// =======================================================================
// function : OSD_MemInfo
// purpose :
@@ -156,29 +147,6 @@ void OSD_MemInfo::Update()
}
#elif defined(__EMSCRIPTEN__)
- if (IsActive (MemHeapUsage)
- || IsActive (MemWorkingSet)
- || IsActive (MemWorkingSetPeak))
- {
- // /proc/%d/status is not emulated - get more info from mallinfo()
- const struct mallinfo aMI = mallinfo();
- if (IsActive (MemHeapUsage))
- {
- myCounters[MemHeapUsage] = aMI.uordblks;
- }
- if (IsActive (MemWorkingSet))
- {
- myCounters[MemWorkingSet] = aMI.uordblks;
- }
- if (IsActive (MemWorkingSetPeak))
- {
- myCounters[MemWorkingSetPeak] = aMI.usmblks;
- }
- }
- if (IsActive (MemVirtual))
- {
- myCounters[MemVirtual] = OSD_MemInfo_getModuleHeapLength();
- }
#elif (defined(__linux__) || defined(__linux))
if (IsActive (MemHeapUsage))
{
+81
View File
@@ -0,0 +1,81 @@
diff --git a/COLLADABaseUtils/include/COLLADABUhash_map.h b/COLLADABaseUtils/include/COLLADABUhash_map.h
index 8ab0fb9b..12503bfb 100644
--- a/COLLADABaseUtils/include/COLLADABUhash_map.h
+++ b/COLLADABaseUtils/include/COLLADABUhash_map.h
@@ -32,9 +32,9 @@
#include <unordered_map>
#include <unordered_set>
- #define COLLADABU_HASH_MAP std::tr1::unordered_map
- #define COLLADABU_HASH_MULTIMAP std::tr1::unordered_multimap
- #define COLLADABU_HASH_SET std::tr1::unordered_set
+ #define COLLADABU_HASH_MAP std::unordered_map
+ #define COLLADABU_HASH_MULTIMAP std::unordered_multimap
+ #define COLLADABU_HASH_SET std::unordered_set
#define COLLADABU_HASH_NAMESPACE_OPEN std { namespace tr1
#define COLLADABU_HASH_NAMESPACE_CLOSE }
#define COLLADABU_HASH_FUN hash
@@ -50,12 +50,12 @@
#define COLLADABU_HASH_FUN hash
#endif
#elif defined(__MINGW32__) || defined(__MINGW64__)
- #include <tr1/unordered_map>
- #include <tr1/unordered_set>
+ #include <unordered_map>
+ #include <unordered_set>
- #define COLLADABU_HASH_MAP std::tr1::unordered_map
- #define COLLADABU_HASH_MULTIMAP std::tr1::unordered_multimap
- #define COLLADABU_HASH_SET std::tr1::unordered_set
+ #define COLLADABU_HASH_MAP std::unordered_map
+ #define COLLADABU_HASH_MULTIMAP std::unordered_multimap
+ #define COLLADABU_HASH_SET std::unordered_set
#define COLLADABU_HASH_NAMESPACE_OPEN std { namespace tr1
#define COLLADABU_HASH_NAMESPACE_CLOSE }
#define COLLADABU_HASH_FUN hash
@@ -107,12 +107,12 @@
#define COLLADABU_HASH_NAMESPACE_CLOSE
#define COLLADABU_HASH_FUN hash
#else
- #include <tr1/unordered_map>
- #include <tr1/unordered_set>
+ #include <unordered_map>
+ #include <unordered_set>
- #define COLLADABU_HASH_MAP std::tr1::unordered_map
- #define COLLADABU_HASH_MULTIMAP std::tr1::unordered_multimap
- #define COLLADABU_HASH_SET std::tr1::unordered_set
+ #define COLLADABU_HASH_MAP std::unordered_map
+ #define COLLADABU_HASH_MULTIMAP std::unordered_multimap
+ #define COLLADABU_HASH_SET std::unordered_set
#define COLLADABU_HASH_NAMESPACE_OPEN std { namespace tr1
#define COLLADABU_HASH_NAMESPACE_CLOSE }
#define COLLADABU_HASH_FUN hash
diff --git a/common/libBuffer/include/CommonFWriteBufferFlusher.h b/common/libBuffer/include/CommonFWriteBufferFlusher.h
index c7af45b2..fac4f133 100644
--- a/common/libBuffer/include/CommonFWriteBufferFlusher.h
+++ b/common/libBuffer/include/CommonFWriteBufferFlusher.h
@@ -15,12 +15,12 @@
#if (defined(WIN64) || defined(_WIN64) || defined(__WIN64__)) || (defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__) || defined(__APPLE__))
#if defined(__GNUC__) && !defined(_LIBCPP_VERSION)
-# include <tr1/unordered_map>
+# include <unordered_map>
#else
# include <unordered_map>
#endif
#else
-# include <tr1/unordered_map>
+# include <unordered_map>
#endif
#ifdef _LIBCPP_VERSION
@@ -58,7 +58,7 @@ namespace Common
#else
typedef __int64 FilePosType;
#endif
- typedef std::tr1::unordered_map<MarkId, FilePosType > MarkIdToFilePos;
+ typedef std::unordered_map<MarkId, FilePosType > MarkIdToFilePos;
public:
static const size_t DEFAUL_BUFFER_SIZE = 64*1024;
+18
View File
@@ -0,0 +1,18 @@
package:
name: ifcopenshell
version: 0.7.0
source:
path: IfcOpenShell
build:
script: |
python nix/build-all.py --without-hdf5 --without-opencollada --without-swig --without-pcre -v --wasm --py310 IfcOpenShell-Python
cp pyodide/setup.py .
about:
home: http://ifcopenshell.org
license: LGPL-3.0-or-later
summary: |
IfcOpenShell is an open source (LGPL) software library for
working with the Industry Foundation Classes (IFC) file format.
+11
View File
@@ -0,0 +1,11 @@
from setuptools import setup, find_packages
setup(name='IfcOpenShell',
version='0.7.0',
description='IfcOpenShell is an open source (LGPL) software library for working with the Industry Foundation Classes (IFC) file format.',
author='Thomas Krijnen',
author_email='thomas@aecgeeks.com',
url='http://ifcopenshell.org',
packages=find_packages(),
package_data={'': ['*.so']},
)
+1
View File
@@ -0,0 +1 @@
.tox
+37
View File
@@ -0,0 +1,37 @@
.PHONY: test
test:
tox
.PHONY: qa
qa:
black .
isort .
pylint ./src --output-format=colorized
.PHONY: typecheck
typecheck:
mypy src/bcf
.PHONY: license
license:
copyright-header --license LGPL3 --copyright-holder "Andrea Ghensi <andrea.ghensi@gmail.com>" --copyright-year "2022" --copyright-software "IfcOpenShell" --copyright-software-description "BCF XML file handling" -a ./ -o ./
.PHONY: coverage
coverage:
coverage run --source bcf -m pytest tests
coverage html
xdg-open htmlcov/index.html
.PHONY: clean
clean:
rm -rf htmlcov
# TODO: make this based on xsd file presence
.PHONY: models
models:
cd src && xsdata generate -p bcf.v2.model --unnest-classes --kw-only --slots -ds Google bcf/v2/xsd
cd src && xsdata generate -p bcf.v3.model --unnest-classes --kw-only --slots -ds Google bcf/v3/xsd
# .PHONY
# api:
# openapi-python-client generate --url https://api.swaggerhub.com/apis/buildingSMART/BCF/3.0
+31 -48
View File
@@ -1,64 +1,54 @@
# bcf
A simple Python implementation of BCF. The data model is described in `data.py`.
A simple Python implementation of BCF.
Manipulation of BCF-XML is available via `bcfxml.py` and manipulation of BCF-API
is available via `bcfapi.py`.
- BCF-XML version 2.1: Fully supported
- BCF-API version 2.1: Not supported, will probably tackle this after BCF-API v3.0
- BCF-XML version 3.0: Almost fully supported, except for the documents module
- BCF-API version 3.0: Almost fully supported, except for two requests.
It tries to support BCF-XML version 2.1 and 3.0, and BCF-API 3.0.
## bcfxml
The `bcfxml` module lets you interact with the BCF-XML standard.
The `bcfxml.load` function lets you read a BCF-XML file.
It takes care of using the right version based on the "bcf.version" file contained in the BCF package.
The BCF files are extracted and parsed on-demand, and edits are stored in memory until you call the `save` method.
```python
from bcf import bcfxml
from bcf.bcfxml import load
# Load a project
bcfxml = bcfxml.load("/path/to/file.bcf")
with load("/path/to/file.bcf") as bcfxml:
project = bcfxml.project
print(project.name)
# To edit a project, just modify the object directly
bcfxml.project.name = "New name"
# The project is also stored in the module
# project == bcfxml.project
project=bcfxml.get_project()
print(project.name)
# Get a dictionary of topics
topics = bcfxml.topics
# To edit a project, just modify the object directly
bcfxml.project.name = "New name"
bcfxml.edit_project()
for topic_handler in bcfxml.topics:
topic = topic_handler.topic
print("Topic guid is", topic.guid)
print("Topic title is", topic.title)
# The BCF file is extracted to this temporary directory
print(bcfxml.filepath)
# Fetch extra data about a topic
header = topic_handler.header
comments = topic_handler.comments
viewpoints = topic_handler.viewpoints
# Get a dictionary of topics
topics = bcfxml.get_topics()
for comment in comments:
print(comment.guid)
print(comment.comment)
print(comment.author)
# Note: topics == bcfxml.topics
for guid, topic in bcfxml.topics.items():
print("Topic guid is", guid)
print("Topic guid is", topic.guid)
print("Topic title is", topic.title)
# Get a particular topic
topic = bcfxml.get_topic(guid)
# Fetch extra data about a topic
header = bcfxml.get_header(guid)
comments = bcfxml.get_comments(guid)
viewpoints = bcfxml.get_viewpoints(guid)
# Modify a topic
topic.title = "New title"
# Note: comments == topic.comments, and so on
for comment_guid, comment in comments.items():
print(comment_guid)
print(comment.comment)
print(comment.author)
# Get a particular topic
topic = bcfxml.get_topic(guid)
# Modify a topic
topic.title = "New title"
bcfxml.edit_topic(topic)
bcfxml.save()
```
## bcfapi
@@ -92,10 +82,3 @@ print(data)
data = bcf_client.get_extensions(project_id)
print(data)
```
## Todo List
The remaining work that needs to be completed in `bcfxml.py` and `bcfapi.py`.
- For `bcfxml.py` two xsds support is remaining namely 'documents.xsd`and`extensions.xsd`.
- For `bcfapi.py` two requests that are `get_topics` and `get_comments` are remaining.
+7
View File
@@ -0,0 +1,7 @@
name: bcf-client
channels:
- conda-forge
dependencies:
- ifcopenshell
- xsdata
- numpy
+116 -2
View File
@@ -1,12 +1,126 @@
[build-system]
requires = [
"setuptools>=42",
"setuptools>=61",
"wheel"
]
build-backend = "setuptools.build_meta"
[project]
name = "bcf-client"
# author = "IfcOpenShell"
description = "BCF-XML file handler."
readme = "README.md"
requires-python = ">=3.8"
keywords = ["IFC", "BCF", "BIM", "eingineering"]
dependencies = [
"xsdata",
"numpy",
]
version = "0.0.1"
classifiers = [
"License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
"Operating System :: OS Independent",
"Programming Language :: Python :: 3",
"Topic :: Scientific/Engineering",
"Topic :: Utilities",
]
[project.urls]
Source = "https://github.com/IfcOpenShell/IfcOpenShell"
Issues = "https://github.com/IfcOpenShell/IfcOpenShell/issues"
[tool.black]
line-length = 120
extend-exclude = "model"
[tool.isort]
profile = "black"
profile = "black"
extend_skip_glob = ["src/bcf/*/model/*"]
[tool.coverage.paths]
source = ["src"]
[tool.coverage.run]
branch = true
source = ["bcf"]
omit = ["*/model/*"]
[tool.coverage.report]
show_missing = true
fail_under = 65
exclude_lines = [
"pragma: no cover",
"if TYPE_CHECKING",
"if __name__ == .__main__.:",
"Protocol",
]
[tool.tox]
legacy_tox_ini = """
[tox]
envlist = py310
isolated_build = true
skip_missing_interpreters = true
requires = tox-conda
[testenv]
conda_deps =
pytest
pytest-cov
coverage
conda_channels =
conda-forge
conda_env = environment.yml
commands = pytest --cov --cov-report=term tests
"""
[tool.mypy]
check_untyped_defs = true
disallow_any_generics = true
disallow_incomplete_defs = true
disallow_subclassing_any = true
disallow_untyped_calls = true
disallow_untyped_defs = true
no_implicit_optional = true
#no_implicit_reexport = true
show_column_numbers = true
show_error_codes = true
show_error_context = true
strict_equality = true
strict_optional = true
warn_redundant_casts = true
#warn_return_any = true
warn_unreachable = true
warn_unused_configs = true
warn_unused_ignores = true
exclude= "src/bcf/v(2|3)/model"
plugins = "numpy.typing.mypy_plugin"
[[tool.mypy.overrides]]
module = "tests"
disallow_untyped_decorators = false
disallow_untyped_defs = false
[[tool.mypy.overrides]]
module = [
"pytest",
"pytest_mock",
"ifcopenshell",
"ifcopenshell.*",
]
ignore_missing_imports = true
[tool.pylint.main]
ignore = ["model"]
ignored-modules = ["bcf.v2.model", "bcf.v3.model", "xsdata"]
jobs = 0
disable="all"
enable="E" # B,B9,BLK,C,D,E,F,I,N,S,W
[tool.pylint.design]
max-args = 10
max-attributes = 10
[tool.pylint.format]
expected-line-ending-format = "LF"
max-line-length = 120
+7
View File
@@ -0,0 +1,7 @@
black
mypy
pylint
isort
xsdata
tox
tox-conda
-1
View File
@@ -1 +0,0 @@
xmlschema
-37
View File
@@ -1,37 +0,0 @@
[metadata]
name=bcf-client
version=0.0.1
author = Ifcopenshell
description = A simple Python implementation of BCF
url = https://github.com/IfcOpenShell/IfcOpenShell
project_urls =
Code=https://github.com/IfcOpenShell/IfcOpenShell
Issues=https://github.com/IfcOpenShell/IfcOpenShell/issues
long_description = file: README.md
long_description_content_type = text/markdown
classifiers =
License :: OSI Approved :: GNU General Public License v3 (GPLv3)
Operating System :: OS Independent
Programming Language :: Python :: 3
Topic :: Scientific/Engineering
Topic :: Utilities
keywords =
Python
file formats
engineering
[options]
package_dir =
= src
packages = find:
python_requires = >=3
install_requires=
xmlschema
include_package_data = True
[options.packages.find]
where = src
[flake8]
max-line-length = 120
ignore = E24, E121, E123, E126, E203, E226, E704, E741, W503, W504
+3
View File
@@ -0,0 +1,3 @@
from setuptools import setup
setup()
+58 -50
View File
@@ -1,61 +1,69 @@
# BCF - BCF Python library
# Copyright (C) 2021 Prabhat Singh <singh01prabhat@gmail.com>
#
# This file is part of BCF.
#
# BCF is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# BCF is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with BCF. If not, see <http://www.gnu.org/licenses/>.
"""
BCF - BCF Python library
Copyright (C) 2021 Prabhat Singh <singh01prabhat@gmail.com>
Copyright (C) 2022 Andrea Ghensi <andrea.ghensi@gmail.com>
This file is part of BCF.
import os.path
BCF is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
BCF is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with BCF. If not, see <http://www.gnu.org/licenses/>.
"""
import zipfile
import tempfile
from xml.dom import minidom
from pathlib import Path
from typing import Optional, Union
from bcf.v2.bcfxml import BcfXml as BcfXml2
from bcf.v3.bcfxml import BcfXml as BcfXml3
from bcf.v3.model import Version
from bcf.xml_parser import AbstractXmlParserSerializer, XmlParserSerializer
def load(filepath):
filepath = extract_project(filepath)
if os.path.isfile(os.path.join(filepath, "bcf.version")):
version_path = os.path.join(filepath, "bcf.version")
version_id = get_version(version_path)
# TODO: we actually coded it for 2.1, let's check the difference between 2.0 and 2.1
if version_id == "2.1" or version_id == "2.0":
from bcf.v2.bcfxml import BcfXml
def load(
filepath: Path, xml_handler: Optional[AbstractXmlParserSerializer] = None
) -> Optional[Union[BcfXml2, BcfXml3]]:
"""
Load a BCF file.
bcfxml = BcfXml()
bcfxml.filepath = filepath
return bcfxml
elif version_id == "3.0":
from bcf.v3.bcfxml import BcfXml
Args:
filepath: The path to the BCF file.
bcfxml = BcfXml()
bcfxml.filepath = filepath
return bcfxml
else:
raise Exception(f"Version {version_id} not supported.")
Returns:
The loaded BCF file.
Raises:
ValueError: If the BCF version is not supported.
"""
xml_handler = xml_handler or XmlParserSerializer()
version_id = _get_version(filepath, xml_handler)
if version_id in {"2.1", "2.0"}:
return BcfXml2.load(filepath, xml_handler)
if version_id == "3.0":
return BcfXml3.load(filepath, xml_handler)
raise ValueError(f"Version {version_id} not supported.")
def get_version(version_path):
xmlparse = minidom.parse(version_path)
version_el = xmlparse.getElementsByTagName("Version")[0]
version = version_el.getAttribute("VersionId")
return version
def _get_version(filepath: Union[str, Path], xml_handler: Optional[AbstractXmlParserSerializer] = None) -> str:
"""
Returns the version of the BCF file.
Args:
filepath: The path to the BCF file.
xml_handler: The XML handler. If none is given, XmlParserSerializer is used.
def extract_project(filepath):
if not filepath:
return
zip_file = zipfile.ZipFile(filepath)
filepath = tempfile.mkdtemp()
zip_file.extractall(filepath)
return filepath
Returns:
The version of the BCF file.
"""
xml_handler = xml_handler or XmlParserSerializer()
with zipfile.ZipFile(filepath) as bcf_zip:
version = xml_handler.parse(bcf_zip.read("bcf.version"), Version)
return version.version_id
+44
View File
@@ -0,0 +1,44 @@
import numpy as np
from numpy.typing import NDArray
def calc_camera_vectors(
elem_placement: NDArray[np.float_],
) -> tuple[NDArray[np.float_], NDArray[np.float_], NDArray[np.float_]]:
"""
Calculate the vectors of a camera pointing to an element.
Args:
elem_placement: Placement matrix of an element.
Returns:
Camera position, direction and up vectors
"""
target_position = elem_placement[:3, 3]
camera_position = target_position + np.array((5, 5, 5))
camera_direction = unit_vector(camera_position - target_position)
camera_right = unit_vector(np.cross(np.array([0.0, 0.0, 1.0]), camera_direction))
camera_up = unit_vector(np.cross(camera_direction, camera_right))
rotation_transform = np.eye(4)
rotation_transform[0, :3] = camera_right
rotation_transform[1, :3] = camera_up
rotation_transform[2, :3] = camera_direction
translation_transform = np.eye(4)
translation_transform[:3, -1] = -camera_position
look_at_transform = np.matmul(rotation_transform, translation_transform)
mat = np.linalg.inv(look_at_transform)
return camera_position, -mat[:3, 2], mat[:3, 1]
def unit_vector(v: NDArray[np.float_]) -> NDArray[np.float_]:
"""
Return the unit vector of a vector.
Args:
v: vector
Returns:
unit vector.
"""
norm = np.linalg.norm(v)
return v if norm == 0 else v / norm
+55
View File
@@ -0,0 +1,55 @@
"""
In Memory Zip File management, taken from ruamel.std.zipfile
Copyright (c) 2017-2020 Anthon van der Neut, Ruamel bvba
original idea from https://stackoverflow.com/a/19722365/1307905
"""
import zipfile
from io import BytesIO
from os import PathLike
from pathlib import Path
from typing import Any, Optional, Protocol
class ZipFileInterface(Protocol):
def writestr(self, filename_in_zip: str | zipfile.ZipInfo, file_contents: bytes | str) -> None:
...
class InMemoryZipFile:
def __init__(
self, file_name: Optional[str | Path] = None, compression: int = zipfile.ZIP_DEFLATED, debug: int = 0
) -> None:
# Create the in-memory file-like object
self._file_name: Optional[str | Path] = str(file_name) if hasattr(file_name, "_from_parts") else file_name
self.in_memory_data = BytesIO()
# Create the in-memory zipfile
self.in_memory_zip = zipfile.ZipFile(self.in_memory_data, "w", compression, False)
self.in_memory_zip.debug = debug
def writestr(self, filename_in_zip: str | zipfile.ZipInfo, file_contents: bytes | str) -> None:
"""Appends a file with name filename_in_zip and contents of
file_contents to the in-memory zip."""
self.in_memory_zip.writestr(filename_in_zip, file_contents)
def write_to_file(self, filename: str | bytes | PathLike[str] | PathLike[bytes] | int) -> None:
"""Writes the in-memory zip to a file."""
# Mark the files as having been created on Windows so that
# Unix permissions are not inferred as 0000
for zfile in self.in_memory_zip.filelist:
zfile.create_system = 0
self.in_memory_zip.close()
with open(filename, "wb") as f:
f.write(self.data)
@property
def data(self) -> bytes:
return self.in_memory_data.getvalue()
def __enter__(self) -> "InMemoryZipFile":
return self
def __exit__(self, exc_type: Any, exc_value: Any, traceback: Any) -> None:
if self._file_name:
self.write_to_file(self._file_name)
View File
-2
View File
@@ -1,4 +1,3 @@
# BCF - BCF Python library
# Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com>
#
@@ -16,4 +15,3 @@
#
# You should have received a copy of the GNU Lesser General Public License
# along with BCF. If not, see <http://www.gnu.org/licenses/>.
File diff suppressed because it is too large Load Diff
-198
View File
@@ -1,198 +0,0 @@
# BCF - BCF Python library
# Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com>
#
# This file is part of BCF.
#
# BCF is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# BCF is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with BCF. If not, see <http://www.gnu.org/licenses/>.
class Project:
def __init__(self):
self.project_id = ""
self.name = ""
self.extension_schema = ""
class BimSnippet:
def __init__(self):
self.snippet_type = None
self.is_external = False
self.reference = None
self.reference_schema = None
class DocumentReference:
def __init__(self):
self.referenced_document = None
self.description = None
self.guid = None
self.is_external = False
class RelatedTopic:
def __init__(self):
self.guid = None
class HeaderFile:
def __init__(self):
self.filename = None
self.date = None
self.reference = None
self.ifc_project = None
self.ifc_spatial_structure_element = None
self.is_external = True
class Header:
def __init__(self):
self.files = []
class Topic:
def __init__(self):
self.reference_links = []
self.title = ""
self.priority = None
self.index = None # Deprecated, stored, but ignored
self.labels = []
self.creation_date = None
self.creation_author = None
self.modified_date = None
self.modified_author = None
self.due_date = None
self.assigned_to = None
self.stage = None
self.description = None
self.bim_snippet = None
self.document_references = []
self.related_topics = []
self.topic_status = None
self.topic_type = None
self.guid = None
self.header = None
self.comments = {}
self.viewpoints = {}
class Comment:
def __init__(self):
self.guid = None
self.date = None
self.author = None
self.comment = None
self.viewpoint = None
self.modified_date = None
self.modified_author = None
self.topic_guid = None # Part of BCF-API
class ViewSetupHints:
def __init__(self):
self.spaces_visible = False
self.space_boundaries_visible = False
self.openings_visible = False
class Component:
def __init__(self):
self.originating_system = None
self.authoring_tool_id = None
self.ifc_guid = None
class ComponentVisibility:
def __init__(self):
self.exceptions = []
self.default_visibility = False
class Color:
def __init__(self):
self.color = None
self.components = []
class Components:
def __init__(self):
self.view_setup_hints = None
self.selection = []
self.visibility = None
self.coloring = []
class Point:
def __init__(self):
self.x = 0
self.y = 0
self.z = 0
class Direction(Point):
pass
class OrthogonalCamera:
def __init__(self):
self.camera_view_point = Point()
self.camera_direction = Direction()
self.camera_up_vector = Direction()
self.view_to_world_scale = 1.0
class PerspectiveCamera:
def __init__(self):
self.camera_view_point = Point()
self.camera_direction = Direction()
self.camera_up_vector = Direction()
self.field_of_view = 60.0
class Line:
def __init__(self):
self.start_point = Point()
self.end_point = Point()
class ClippingPlane:
def __init__(self):
self.location = Point()
self.direction = Direction()
class Bitmap:
def __init__(self):
self.reference = "" # Only in BCF-XML
self.bitmap_data = None # Only in BCF-API
self.bitmap_format = "PNG" # Enum of png or jpg
self.location = Point()
self.normal = Direction()
self.up = Direction()
self.height = 1.0
class Viewpoint:
def __init__(self):
self.guid = None
self.viewpoint = None
self.snapshot = None
self.index = None
self.components = None # It's not a list, despite the plural name
self.orthogonal_camera = None
self.perspective_camera = None
self.lines = []
self.clipping_planes = []
self.bitmaps = []
+70
View File
@@ -0,0 +1,70 @@
from bcf.v2.model.markup import (
BimSnippet,
Comment,
CommentViewpoint,
Header,
HeaderFile,
Markup,
Topic,
TopicDocumentReference,
TopicRelatedTopic,
ViewPoint,
)
from bcf.v2.model.project import Project, ProjectExtension
from bcf.v2.model.version import Version
from bcf.v2.model.visinfo import (
BitmapFormat,
ClippingPlane,
Component,
ComponentColoring,
ComponentColoringColor,
Components,
ComponentSelection,
ComponentVisibility,
ComponentVisibilityExceptions,
Direction,
Line,
OrthogonalCamera,
PerspectiveCamera,
Point,
ViewSetupHints,
VisualizationInfo,
VisualizationInfoBitmap,
VisualizationInfoClippingPlanes,
VisualizationInfoLines,
)
__all__ = [
"BimSnippet",
"Comment",
"CommentViewpoint",
"Header",
"HeaderFile",
"Markup",
"Topic",
"TopicDocumentReference",
"TopicRelatedTopic",
"ViewPoint",
"Project",
"ProjectExtension",
"Version",
"BitmapFormat",
"ClippingPlane",
"Component",
"ComponentColoring",
"ComponentColoringColor",
"ComponentSelection",
"ComponentVisibility",
"ComponentVisibilityExceptions",
"Components",
"Direction",
"Line",
"OrthogonalCamera",
"PerspectiveCamera",
"Point",
"ViewSetupHints",
"VisualizationInfo",
"VisualizationInfoBitmap",
"VisualizationInfoClippingPlanes",
"VisualizationInfoLines",
]
+461
View File
@@ -0,0 +1,461 @@
from dataclasses import dataclass, field
from typing import List, Optional
from xsdata.models.datatype import XmlDateTime
@dataclass(slots=True, kw_only=True)
class BimSnippet:
reference: str = field(
metadata={
"name": "Reference",
"type": "Element",
"namespace": "",
"required": True,
}
)
reference_schema: str = field(
metadata={
"name": "ReferenceSchema",
"type": "Element",
"namespace": "",
"required": True,
}
)
snippet_type: str = field(
metadata={
"name": "SnippetType",
"type": "Attribute",
"required": True,
}
)
is_external: bool = field(
default=False,
metadata={
"name": "isExternal",
"type": "Attribute",
}
)
@dataclass(slots=True, kw_only=True)
class CommentViewpoint:
class Meta:
global_type = False
guid: str = field(
metadata={
"name": "Guid",
"type": "Attribute",
"required": True,
"pattern": r"[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}",
}
)
@dataclass(slots=True, kw_only=True)
class HeaderFile:
class Meta:
global_type = False
filename: Optional[str] = field(
default=None,
metadata={
"name": "Filename",
"type": "Element",
"namespace": "",
}
)
date: Optional[XmlDateTime] = field(
default=None,
metadata={
"name": "Date",
"type": "Element",
"namespace": "",
}
)
reference: Optional[str] = field(
default=None,
metadata={
"name": "Reference",
"type": "Element",
"namespace": "",
}
)
ifc_project: Optional[str] = field(
default=None,
metadata={
"name": "IfcProject",
"type": "Attribute",
"length": 22,
"pattern": r"[0-9,A-Z,a-z,_$]*",
}
)
ifc_spatial_structure_element: Optional[str] = field(
default=None,
metadata={
"name": "IfcSpatialStructureElement",
"type": "Attribute",
"length": 22,
"pattern": r"[0-9,A-Z,a-z,_$]*",
}
)
is_external: bool = field(
default=True,
metadata={
"name": "isExternal",
"type": "Attribute",
}
)
@dataclass(slots=True, kw_only=True)
class TopicDocumentReference:
class Meta:
global_type = False
referenced_document: Optional[str] = field(
default=None,
metadata={
"name": "ReferencedDocument",
"type": "Element",
"namespace": "",
}
)
description: Optional[str] = field(
default=None,
metadata={
"name": "Description",
"type": "Element",
"namespace": "",
}
)
guid: Optional[str] = field(
default=None,
metadata={
"name": "Guid",
"type": "Attribute",
"pattern": r"[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}",
}
)
is_external: bool = field(
default=False,
metadata={
"name": "isExternal",
"type": "Attribute",
}
)
@dataclass(slots=True, kw_only=True)
class TopicRelatedTopic:
class Meta:
global_type = False
guid: str = field(
metadata={
"name": "Guid",
"type": "Attribute",
"required": True,
"pattern": r"[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}",
}
)
@dataclass(slots=True, kw_only=True)
class ViewPoint:
viewpoint: Optional[str] = field(
default=None,
metadata={
"name": "Viewpoint",
"type": "Element",
"namespace": "",
}
)
snapshot: Optional[str] = field(
default=None,
metadata={
"name": "Snapshot",
"type": "Element",
"namespace": "",
}
)
index: Optional[int] = field(
default=None,
metadata={
"name": "Index",
"type": "Element",
"namespace": "",
}
)
guid: str = field(
metadata={
"name": "Guid",
"type": "Attribute",
"required": True,
"pattern": r"[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}",
}
)
@dataclass(slots=True, kw_only=True)
class Comment:
date: XmlDateTime = field(
metadata={
"name": "Date",
"type": "Element",
"namespace": "",
"required": True,
}
)
author: str = field(
metadata={
"name": "Author",
"type": "Element",
"namespace": "",
"required": True,
}
)
comment: str = field(
metadata={
"name": "Comment",
"type": "Element",
"namespace": "",
"required": True,
}
)
viewpoint: Optional[CommentViewpoint] = field(
default=None,
metadata={
"name": "Viewpoint",
"type": "Element",
"namespace": "",
}
)
modified_date: Optional[XmlDateTime] = field(
default=None,
metadata={
"name": "ModifiedDate",
"type": "Element",
"namespace": "",
}
)
modified_author: Optional[str] = field(
default=None,
metadata={
"name": "ModifiedAuthor",
"type": "Element",
"namespace": "",
}
)
guid: str = field(
metadata={
"name": "Guid",
"type": "Attribute",
"required": True,
"pattern": r"[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}",
}
)
@dataclass(slots=True, kw_only=True)
class Header:
file: List[HeaderFile] = field(
default_factory=list,
metadata={
"name": "File",
"type": "Element",
"namespace": "",
"min_occurs": 1,
}
)
@dataclass(slots=True, kw_only=True)
class Topic:
reference_link: List[str] = field(
default_factory=list,
metadata={
"name": "ReferenceLink",
"type": "Element",
"namespace": "",
}
)
title: str = field(
metadata={
"name": "Title",
"type": "Element",
"namespace": "",
"required": True,
}
)
priority: Optional[str] = field(
default=None,
metadata={
"name": "Priority",
"type": "Element",
"namespace": "",
}
)
index: Optional[int] = field(
default=None,
metadata={
"name": "Index",
"type": "Element",
"namespace": "",
}
)
labels: List[str] = field(
default_factory=list,
metadata={
"name": "Labels",
"type": "Element",
"namespace": "",
}
)
creation_date: XmlDateTime = field(
metadata={
"name": "CreationDate",
"type": "Element",
"namespace": "",
"required": True,
}
)
creation_author: str = field(
metadata={
"name": "CreationAuthor",
"type": "Element",
"namespace": "",
"required": True,
}
)
modified_date: Optional[XmlDateTime] = field(
default=None,
metadata={
"name": "ModifiedDate",
"type": "Element",
"namespace": "",
}
)
modified_author: Optional[str] = field(
default=None,
metadata={
"name": "ModifiedAuthor",
"type": "Element",
"namespace": "",
}
)
due_date: Optional[XmlDateTime] = field(
default=None,
metadata={
"name": "DueDate",
"type": "Element",
"namespace": "",
}
)
assigned_to: Optional[str] = field(
default=None,
metadata={
"name": "AssignedTo",
"type": "Element",
"namespace": "",
}
)
stage: Optional[str] = field(
default=None,
metadata={
"name": "Stage",
"type": "Element",
"namespace": "",
}
)
description: Optional[str] = field(
default=None,
metadata={
"name": "Description",
"type": "Element",
"namespace": "",
}
)
bim_snippet: Optional[BimSnippet] = field(
default=None,
metadata={
"name": "BimSnippet",
"type": "Element",
"namespace": "",
}
)
document_reference: List[TopicDocumentReference] = field(
default_factory=list,
metadata={
"name": "DocumentReference",
"type": "Element",
"namespace": "",
}
)
related_topic: List[TopicRelatedTopic] = field(
default_factory=list,
metadata={
"name": "RelatedTopic",
"type": "Element",
"namespace": "",
}
)
guid: str = field(
metadata={
"name": "Guid",
"type": "Attribute",
"required": True,
"pattern": r"[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}",
}
)
topic_type: Optional[str] = field(
default=None,
metadata={
"name": "TopicType",
"type": "Attribute",
}
)
topic_status: Optional[str] = field(
default=None,
metadata={
"name": "TopicStatus",
"type": "Attribute",
}
)
@dataclass(slots=True, kw_only=True)
class Markup:
header: Optional[Header] = field(
default=None,
metadata={
"name": "Header",
"type": "Element",
"namespace": "",
}
)
topic: Topic = field(
metadata={
"name": "Topic",
"type": "Element",
"namespace": "",
"required": True,
}
)
comment: List[Comment] = field(
default_factory=list,
metadata={
"name": "Comment",
"type": "Element",
"namespace": "",
}
)
viewpoints: List[ViewPoint] = field(
default_factory=list,
metadata={
"name": "Viewpoints",
"type": "Element",
"namespace": "",
}
)
+41
View File
@@ -0,0 +1,41 @@
from dataclasses import dataclass, field
from typing import Optional
@dataclass(slots=True, kw_only=True)
class Project:
name: Optional[str] = field(
default=None,
metadata={
"name": "Name",
"type": "Element",
"namespace": "",
}
)
project_id: str = field(
metadata={
"name": "ProjectId",
"type": "Attribute",
"required": True,
}
)
@dataclass(slots=True, kw_only=True)
class ProjectExtension:
project: Optional[Project] = field(
default=None,
metadata={
"name": "Project",
"type": "Element",
"namespace": "",
}
)
extension_schema: str = field(
metadata={
"name": "ExtensionSchema",
"type": "Element",
"namespace": "",
"required": True,
}
)
+21
View File
@@ -0,0 +1,21 @@
from dataclasses import dataclass, field
from typing import Optional
@dataclass(slots=True, kw_only=True)
class Version:
detailed_version: Optional[str] = field(
default=None,
metadata={
"name": "DetailedVersion",
"type": "Element",
"namespace": "",
}
)
version_id: Optional[str] = field(
default=None,
metadata={
"name": "VersionId",
"type": "Attribute",
}
)
+476
View File
@@ -0,0 +1,476 @@
from dataclasses import dataclass, field
from enum import Enum
from typing import List, Optional
class BitmapFormat(Enum):
PNG = "PNG"
JPG = "JPG"
@dataclass(slots=True, kw_only=True)
class Component:
originating_system: Optional[str] = field(
default=None,
metadata={
"name": "OriginatingSystem",
"type": "Element",
}
)
authoring_tool_id: Optional[str] = field(
default=None,
metadata={
"name": "AuthoringToolId",
"type": "Element",
}
)
ifc_guid: Optional[str] = field(
default=None,
metadata={
"name": "IfcGuid",
"type": "Attribute",
"length": 22,
"pattern": r"[0-9,A-Z,a-z,_$]*",
}
)
@dataclass(slots=True, kw_only=True)
class Direction:
x: float = field(
metadata={
"name": "X",
"type": "Element",
"required": True,
}
)
y: float = field(
metadata={
"name": "Y",
"type": "Element",
"required": True,
}
)
z: float = field(
metadata={
"name": "Z",
"type": "Element",
"required": True,
}
)
@dataclass(slots=True, kw_only=True)
class Point:
x: float = field(
metadata={
"name": "X",
"type": "Element",
"required": True,
}
)
y: float = field(
metadata={
"name": "Y",
"type": "Element",
"required": True,
}
)
z: float = field(
metadata={
"name": "Z",
"type": "Element",
"required": True,
}
)
@dataclass(slots=True, kw_only=True)
class ViewSetupHints:
spaces_visible: Optional[bool] = field(
default=None,
metadata={
"name": "SpacesVisible",
"type": "Attribute",
}
)
space_boundaries_visible: Optional[bool] = field(
default=None,
metadata={
"name": "SpaceBoundariesVisible",
"type": "Attribute",
}
)
openings_visible: Optional[bool] = field(
default=None,
metadata={
"name": "OpeningsVisible",
"type": "Attribute",
}
)
@dataclass(slots=True, kw_only=True)
class ClippingPlane:
location: Point = field(
metadata={
"name": "Location",
"type": "Element",
"required": True,
}
)
direction: Direction = field(
metadata={
"name": "Direction",
"type": "Element",
"required": True,
}
)
@dataclass(slots=True, kw_only=True)
class ComponentColoringColor:
class Meta:
global_type = False
component: List[Component] = field(
default_factory=list,
metadata={
"name": "Component",
"type": "Element",
"min_occurs": 1,
}
)
color: Optional[str] = field(
default=None,
metadata={
"name": "Color",
"type": "Attribute",
"pattern": r"[0-9,a-f,A-F]{6}([0-9,a-f,A-F]{2})?",
}
)
@dataclass(slots=True, kw_only=True)
class ComponentSelection:
component: List[Component] = field(
default_factory=list,
metadata={
"name": "Component",
"type": "Element",
"min_occurs": 1,
}
)
@dataclass(slots=True, kw_only=True)
class ComponentVisibilityExceptions:
class Meta:
global_type = False
component: List[Component] = field(
default_factory=list,
metadata={
"name": "Component",
"type": "Element",
"min_occurs": 1,
}
)
@dataclass(slots=True, kw_only=True)
class Line:
start_point: Point = field(
metadata={
"name": "StartPoint",
"type": "Element",
"required": True,
}
)
end_point: Point = field(
metadata={
"name": "EndPoint",
"type": "Element",
"required": True,
}
)
@dataclass(slots=True, kw_only=True)
class OrthogonalCamera:
"""
Attributes
camera_view_point:
camera_direction:
camera_up_vector:
view_to_world_scale: view's visible size in meters
"""
camera_view_point: Point = field(
metadata={
"name": "CameraViewPoint",
"type": "Element",
"required": True,
}
)
camera_direction: Direction = field(
metadata={
"name": "CameraDirection",
"type": "Element",
"required": True,
}
)
camera_up_vector: Direction = field(
metadata={
"name": "CameraUpVector",
"type": "Element",
"required": True,
}
)
view_to_world_scale: float = field(
metadata={
"name": "ViewToWorldScale",
"type": "Element",
"required": True,
}
)
@dataclass(slots=True, kw_only=True)
class PerspectiveCamera:
"""
Attributes
camera_view_point:
camera_direction:
camera_up_vector:
field_of_view: It is currently limited to a value between 45 and
60 degrees. This limitation will be dropped in the next
release and viewers should be expect values outside this
range in current implementations.
"""
camera_view_point: Point = field(
metadata={
"name": "CameraViewPoint",
"type": "Element",
"required": True,
}
)
camera_direction: Direction = field(
metadata={
"name": "CameraDirection",
"type": "Element",
"required": True,
}
)
camera_up_vector: Direction = field(
metadata={
"name": "CameraUpVector",
"type": "Element",
"required": True,
}
)
field_of_view: float = field(
metadata={
"name": "FieldOfView",
"type": "Element",
"required": True,
"min_inclusive": 1.0,
"max_inclusive": 170.0,
}
)
@dataclass(slots=True, kw_only=True)
class VisualizationInfoBitmap:
class Meta:
global_type = False
bitmap: BitmapFormat = field(
metadata={
"name": "Bitmap",
"type": "Element",
"required": True,
}
)
reference: str = field(
metadata={
"name": "Reference",
"type": "Element",
"required": True,
}
)
location: Point = field(
metadata={
"name": "Location",
"type": "Element",
"required": True,
}
)
normal: Direction = field(
metadata={
"name": "Normal",
"type": "Element",
"required": True,
}
)
up: Direction = field(
metadata={
"name": "Up",
"type": "Element",
"required": True,
}
)
height: float = field(
metadata={
"name": "Height",
"type": "Element",
"required": True,
}
)
@dataclass(slots=True, kw_only=True)
class ComponentColoring:
color: List[ComponentColoringColor] = field(
default_factory=list,
metadata={
"name": "Color",
"type": "Element",
"min_occurs": 1,
}
)
@dataclass(slots=True, kw_only=True)
class ComponentVisibility:
exceptions: Optional[ComponentVisibilityExceptions] = field(
default=None,
metadata={
"name": "Exceptions",
"type": "Element",
}
)
default_visibility: Optional[bool] = field(
default=None,
metadata={
"name": "DefaultVisibility",
"type": "Attribute",
}
)
@dataclass(slots=True, kw_only=True)
class VisualizationInfoClippingPlanes:
class Meta:
global_type = False
clipping_plane: List[ClippingPlane] = field(
default_factory=list,
metadata={
"name": "ClippingPlane",
"type": "Element",
}
)
@dataclass(slots=True, kw_only=True)
class VisualizationInfoLines:
class Meta:
global_type = False
line: List[Line] = field(
default_factory=list,
metadata={
"name": "Line",
"type": "Element",
"min_occurs": 1,
}
)
@dataclass(slots=True, kw_only=True)
class Components:
view_setup_hints: Optional[ViewSetupHints] = field(
default=None,
metadata={
"name": "ViewSetupHints",
"type": "Element",
}
)
selection: Optional[ComponentSelection] = field(
default=None,
metadata={
"name": "Selection",
"type": "Element",
}
)
visibility: ComponentVisibility = field(
metadata={
"name": "Visibility",
"type": "Element",
"required": True,
}
)
coloring: Optional[ComponentColoring] = field(
default=None,
metadata={
"name": "Coloring",
"type": "Element",
}
)
@dataclass(slots=True, kw_only=True)
class VisualizationInfo:
"""
VisualizationInfo documentation.
"""
components: Optional[Components] = field(
default=None,
metadata={
"name": "Components",
"type": "Element",
}
)
orthogonal_camera: Optional[OrthogonalCamera] = field(
default=None,
metadata={
"name": "OrthogonalCamera",
"type": "Element",
}
)
perspective_camera: Optional[PerspectiveCamera] = field(
default=None,
metadata={
"name": "PerspectiveCamera",
"type": "Element",
}
)
lines: Optional[VisualizationInfoLines] = field(
default=None,
metadata={
"name": "Lines",
"type": "Element",
}
)
clipping_planes: Optional[VisualizationInfoClippingPlanes] = field(
default=None,
metadata={
"name": "ClippingPlanes",
"type": "Element",
}
)
bitmap: List[VisualizationInfoBitmap] = field(
default_factory=list,
metadata={
"name": "Bitmap",
"type": "Element",
}
)
guid: str = field(
metadata={
"name": "Guid",
"type": "Attribute",
"required": True,
"pattern": r"[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}",
}
)
+240
View File
@@ -0,0 +1,240 @@
"""BCF XML V2 Topic handler."""
import datetime
import uuid
import zipfile
from pathlib import Path
from typing import Any, Optional
from ifcopenshell import entity_instance
from xsdata.models.datatype import XmlDateTime
import bcf.v2.model as mdl
from bcf.inmemory_zipfile import ZipFileInterface
from bcf.v2.visinfo import VisualizationInfoHandler
from bcf.xml_parser import AbstractXmlParserSerializer, XmlParserSerializer
class TopicHandler:
"""BCF Topic and related objects handler."""
def __init__(
self,
topic_dir: Optional[zipfile.Path] = None,
xml_handler: Optional[AbstractXmlParserSerializer] = None,
) -> None:
self._markup: Optional[mdl.Markup] = None
self._viewpoints: dict[str, VisualizationInfoHandler] = {}
self._reference_files: dict[str, bytes] = {}
self._document_references: dict[str, bytes] = {}
self._bim_snippet: Optional[bytes] = None
self._xml_handler = xml_handler or XmlParserSerializer()
self._topic_dir = topic_dir
@property
def markup(self) -> Optional[mdl.Markup]:
if not self._markup:
markup_path = self._topic_dir.joinpath("markup.bcf")
if markup_path.exists():
self._markup = self._xml_handler.parse(markup_path.read_bytes(), mdl.Markup)
return self._markup
@markup.setter
def markup(self, value: mdl.Markup) -> None:
self._markup = value
@property
def topic(self) -> mdl.Topic:
"""Return the Topic object."""
return self.markup.topic
@property
def guid(self) -> str:
"""Return the GUID of the topic."""
if self._markup:
return self.topic.guid
return self._topic_dir.name if self._topic_dir else ""
@property
def header(self) -> Optional[mdl.Header]:
"""Return the header of the topic."""
return self.markup.header if self.markup else None
@property
def comments(self) -> list[mdl.Comment]:
"""Return the comments of the topic."""
return self.markup.comment if self.markup else []
@property
def bim_snippet(self) -> Optional[bytes]:
if not self._bim_snippet and self._topic_dir:
self._bim_snippet = self._load_bim_snippet()
return self._bim_snippet
@bim_snippet.setter
def bim_snippet(self, value: bytes) -> None:
self._bim_snippet = value
@property
def viewpoints(self) -> dict[str, VisualizationInfoHandler]:
if not self._viewpoints and self._topic_dir:
self._viewpoints = self._load_viewpoints()
return self._viewpoints
@property
def reference_files(self) -> dict[str, bytes]:
if self._reference_files or not self.header:
return self._reference_files
for ref in self.header.file:
if ref.is_external:
continue
real_path = self._topic_dir
for path_part in ref.reference.split("/"):
real_path = real_path.parent if path_part == ".." else real_path.joinpath(path_part)
self._reference_files[ref.reference] = real_path.read_bytes()
return self._reference_files
@property
def document_references(self) -> dict[str, bytes]:
if self._document_references or not self.topic:
return self._document_references
for doc in self.topic.document_reference:
if doc.is_external or not doc.referenced_document:
continue
real_path = self._topic_dir
for path_part in doc.referenced_document.split("/"):
real_path = real_path.parent if path_part == ".." else real_path.joinpath(path_part)
self._document_references[doc.referenced_document] = real_path.read_bytes()
return self._document_references
def _load_bim_snippet(self) -> Optional[bytes]:
bim_snippet_obj = self.topic.bim_snippet
if bim_snippet_obj and not bim_snippet_obj.is_external:
bim_snippet_path = self._topic_dir.joinpath(bim_snippet_obj.reference)
if bim_snippet_path.exists():
return bim_snippet_path.read_bytes()
return None
def _load_viewpoints(self) -> dict[str, VisualizationInfoHandler]:
if self.markup and (viewpoints := self.markup.viewpoints):
return VisualizationInfoHandler.from_topic_viewpoints(self._topic_dir, viewpoints)
return {}
@classmethod
def create_new(
cls,
title: str,
description: str,
author: str,
topic_type: str = "",
topic_status: str = "",
xml_handler: Optional[AbstractXmlParserSerializer] = None,
) -> "TopicHandler":
"""
Create a new BCF topic.
Args:
title: The title of the topic.
description: The description of the topic.
author: The author of the topic.
topic_type: The type of the topic.
topic_status: The status of the topic.
xml_handler: The XML parser/serializer to use.
Returns:
The BCF topic definition.
"""
creation_date = XmlDateTime.from_datetime(datetime.datetime.now())
guid = str(uuid.uuid4())
topic = mdl.Topic(
title=title,
description=description,
creation_author=author,
creation_date=creation_date,
guid=guid,
topic_type=topic_type,
topic_status=topic_status,
)
markup = mdl.Markup(topic=topic)
obj = cls(topic_dir=Path(guid), xml_handler=xml_handler or XmlParserSerializer())
obj.markup = markup
return obj
def save(self, destination_zip: ZipFileInterface) -> None:
"""
Save the topic to a BCF zip file.
Args:
bcf_zip: The BCF zip file to save to.
"""
topic_dir = self.guid
self._save_xml(destination_zip, self._markup, "markup.bcf")
self._save_viewpoints(destination_zip, topic_dir)
self._save_bim_snippet(destination_zip)
self._save_reference_files(destination_zip)
self._save_document_references(destination_zip)
def _save_viewpoints(self, destination_zip: ZipFileInterface, topic_dir: str) -> None:
if not self.markup or not (viewpoints := self.markup.viewpoints):
return
for vpt in viewpoints:
if vpt.viewpoint:
self.viewpoints[vpt.viewpoint].save(destination_zip, topic_dir, vpt)
def _save_xml(self, destination_zip: ZipFileInterface, item: Any, target: str) -> None:
if self._topic_dir is None:
return
to_write = self._xml_handler.serialize(item) if item else self._topic_dir.joinpath(target).read_bytes()
destination_zip.writestr(f"{self._topic_dir.name}/{target}", to_write)
def _save_bim_snippet(self, destination_zip: ZipFileInterface) -> None:
snippet = self.topic.bim_snippet
if not snippet or snippet.is_external:
return
ref_filename = Path(snippet.reference).name
if self.bim_snippet:
destination_zip.writestr(f"{self.topic.guid}/{ref_filename}", self.bim_snippet)
def _save_reference_files(self, destination_zip: ZipFileInterface) -> None:
if not self.header:
return
for ref in self.header.file:
if ref.is_external or not ref.reference:
continue
real_path = self._topic_dir
for path_part in ref.reference.split("/"):
real_path = real_path.parent if path_part == ".." else real_path.joinpath(path_part)
destination_zip.writestr(real_path.at, self.reference_files[ref.reference])
def _save_document_references(self, destination_zip: ZipFileInterface) -> None:
if not self.topic:
return
for doc in self.topic.document_reference:
if doc.is_external or not doc.referenced_document:
continue
real_path = self._topic_dir
for path_part in doc.referenced_document.split("/"):
real_path = real_path.parent if path_part == ".." else real_path.joinpath(path_part)
destination_zip.writestr(real_path.at, self.document_references[doc.referenced_document])
def add_viewpoint(self, element: entity_instance) -> None:
"""
Add a viewpoint tergeting an IFC element to the topic.
Args:
element: The IFC element.
"""
new_viewpoint = VisualizationInfoHandler.create_new(element, self._xml_handler)
self.add_visinfo_handler(new_viewpoint)
def add_visinfo_handler(self, new_viewpoint: VisualizationInfoHandler) -> None:
self.viewpoints[new_viewpoint.guid] = new_viewpoint
self.markup.viewpoints.append(mdl.ViewPoint(viewpoint=new_viewpoint.guid, guid=new_viewpoint.guid))
def __eq__(self, other: object) -> bool:
if not isinstance(other, TopicHandler):
raise TypeError("Equality needs a BcfXml object.")
return (
self.markup == other.markup
and self.viewpoints == other.viewpoints
and self.bim_snippet == other.bim_snippet
)
+246
View File
@@ -0,0 +1,246 @@
import uuid
import zipfile
from functools import lru_cache
from typing import Any, Iterable, Optional
import numpy as np
from ifcopenshell import entity_instance
from ifcopenshell.util import placement
from numpy.typing import NDArray
import bcf.v2.model as mdl
from bcf.geometry import calc_camera_vectors
from bcf.inmemory_zipfile import ZipFileInterface
from bcf.xml_parser import AbstractXmlParserSerializer, XmlParserSerializer
class VisualizationInfoHandler:
"""Handle the VisualizationInfo and related objects."""
def __init__(
self,
visualization_info: mdl.VisualizationInfo,
snapshot: Optional[bytes] = None,
bitmaps: Optional[dict[str, bytes]] = None,
xml_handler: Optional[AbstractXmlParserSerializer] = None,
) -> None:
self.visualization_info = visualization_info
self.snapshot = snapshot
self.bitmaps = bitmaps or {}
self._xml_handler = xml_handler or XmlParserSerializer()
@property
def guid(self) -> str:
"""Return the GUID of the visualization info."""
return self.visualization_info.guid
@classmethod
def from_topic_viewpoints(
cls,
topic_dir: zipfile.Path,
vps: Iterable[mdl.ViewPoint],
xml_handler: Optional[AbstractXmlParserSerializer] = None,
) -> dict[str, "VisualizationInfoHandler"]:
"""Create VisualizationInfoHandler objects of a Topic's ViewPoints."""
viewpoints = {}
for vpt in vps:
visinfo = cls.load(topic_dir, vpt, xml_handler)
if visinfo and vpt.viewpoint:
viewpoints[vpt.viewpoint] = visinfo
return viewpoints
@classmethod
def load(
cls,
topic_dir: zipfile.Path,
vpt: mdl.ViewPoint,
xml_handler: Optional[AbstractXmlParserSerializer] = None,
) -> Optional["VisualizationInfoHandler"]:
"""
Load the VisualizationInfo and related objects from a BCF zip file.
Args:
topic_dir: The directory in the BCF zip file to load from.
vpt: The ViewPoint to load.
xml_handler: The XML handler to use to parse the VisualizationInfo.
Returns:
The VisualizationInfoHandler object.
"""
visinfo = cls._load_visinfo(topic_dir, vpt.viewpoint, xml_handler)
if not visinfo:
return None
snapshot = cls._load_snapshot(topic_dir, vpt.snapshot)
bitmaps = cls._load_bitmaps(topic_dir, visinfo)
return cls(visinfo, snapshot, bitmaps, xml_handler)
@staticmethod
def _load_visinfo(
topic_dir: zipfile.Path,
vp_name: Optional[str],
xml_handler: Optional[AbstractXmlParserSerializer] = None,
) -> Optional[mdl.VisualizationInfo]:
if not vp_name:
return None
vp_path = topic_dir.joinpath(vp_name)
if vp_path.exists():
xml_handler = xml_handler or XmlParserSerializer()
return xml_handler.parse(vp_path.read_bytes(), mdl.VisualizationInfo)
return None
@staticmethod
def _load_snapshot(topic_dir: zipfile.Path, vp_snapshot: Optional[str]) -> Optional[bytes]:
if vp_snapshot:
snapshot_path = topic_dir.joinpath(vp_snapshot)
if snapshot_path.exists():
return snapshot_path.read_bytes()
return None
@staticmethod
def _load_bitmaps(topic_dir: zipfile.Path, visinfo: Optional[mdl.VisualizationInfo]) -> dict[str, bytes]:
if not visinfo or not (bitmaps := visinfo.bitmap):
return {}
bitmaps_dict = {}
for bitmap in bitmaps:
if not bitmap.reference:
continue
bitmap_path = topic_dir.joinpath(bitmap.reference)
if bitmap_path.exists():
bitmaps_dict[bitmap.reference] = bitmap_path.read_bytes()
return bitmaps_dict
def save(
self,
bcf_zip: ZipFileInterface,
topic_dir: str,
vpt: mdl.ViewPoint,
) -> None:
"""
Save the VisualizationInfo and related objects to a BCF zip file.
Args:
bcf_zip: The BCF zip file to save to.
topic_dir: The directory in the BCF zip file to save to.
vpt: The ViewPoint to save.
"""
if not (vp_name := vpt.viewpoint):
return
self._save_visinfo(bcf_zip, topic_dir, vp_name)
self._save_snapshot(bcf_zip, topic_dir, vpt.snapshot)
self._save_bitmaps(bcf_zip, topic_dir)
def _save_snapshot(self, bcf_zip: ZipFileInterface, topic_dir: str, filename: Optional[str]) -> None:
if self.snapshot and filename:
bcf_zip.writestr(f"{topic_dir}/{filename}", self.snapshot)
def _save_visinfo(self, bcf_zip: ZipFileInterface, topic_dir: str, vp_name: str) -> None:
bcf_zip.writestr(
f"{topic_dir}/{vp_name}",
self._xml_handler.serialize(self.visualization_info),
)
def _save_bitmaps(self, bcf_zip: ZipFileInterface, topic_dir: str) -> None:
if not self.bitmaps:
return
if not (bitmaps_defs := self.visualization_info.bitmap):
return
for bitmap_def in bitmaps_defs:
if not (bitmap_name := bitmap_def.reference):
continue
if bitmap_name in self.bitmaps:
bcf_zip.writestr(f"{topic_dir}/{bitmap_name}", self.bitmaps[bitmap_name])
@classmethod
def create_new(
cls,
element: entity_instance,
xml_handler: Optional[AbstractXmlParserSerializer] = None,
) -> "VisualizationInfoHandler":
"""
Create a new VisualizationInfoHandler object from an IFC element.
Args:
element: The IFC element to point at.
xml_handler: The XML handler to use.
Returns:
The VisualizationInfoHandler object.
"""
xml_handler = xml_handler or XmlParserSerializer()
return cls(visualization_info=build_viewpoint(element), xml_handler=xml_handler)
@lru_cache(maxsize=None)
def build_viewpoint(element: entity_instance) -> mdl.VisualizationInfo:
"""
Return a BCF viewpoint of an IFC element.
This function is cached to speedudp the creation of multiple BCF topics regarding the same element.
Args:
element: The IFC element to point at.
Returns:
The BCF viewpoint definition.
"""
elem_placement = placement.get_local_placement(element.ObjectPlacement)
return mdl.VisualizationInfo(
guid=str(uuid.uuid4()),
components=build_components(element.GlobalId),
perspective_camera=build_camera(elem_placement),
)
def build_components(guid: str) -> mdl.Components:
"""
Return the BCF components from an IFC element GUID.
Args:
guid: The IFC element GUID.
Returns:
The BCF components definition.
"""
return mdl.Components(
selection=mdl.ComponentSelection(component=[mdl.Component(ifc_guid=guid)]),
visibility=mdl.ComponentVisibility(default_visibility=True),
)
def build_camera(elem_placement: NDArray[np.float_]) -> mdl.PerspectiveCamera:
"""
Return a BCF camera for an IFC element placement matrix.
Args:
elem_placement: The IFC element placement as a rototranslation matrix.
Returns:
The BCF camera definition.
"""
return build_camera_from_vectors(*calc_camera_vectors(elem_placement))
def build_camera_from_vectors(
camera_position: NDArray[np.float_], camera_dir: NDArray[np.float_], camera_up: NDArray[np.float_]
) -> mdl.PerspectiveCamera:
"""
Return a BCF camera for an IFC element placement matrix.
Args:
camera_position: camera position array
camera_dir: camera direction versor
camera_up_vector: camera up versor
Returns:
The BCF camera definition.
"""
camera_viewpoint = mdl.Point(x=camera_position[0], y=camera_position[1], z=camera_position[2])
camera_direction = mdl.Direction(x=camera_dir[0], y=camera_dir[1], z=camera_dir[2])
camera_up_vector = mdl.Direction(x=camera_up[0], y=camera_up[1], z=camera_up[2])
return mdl.PerspectiveCamera(
camera_view_point=camera_viewpoint,
camera_direction=camera_direction,
camera_up_vector=camera_up_vector,
field_of_view=60.0,
)
+1 -19
View File
@@ -1,19 +1 @@
# BCF - BCF Python library
# Copyright (C) 2021 Prabhat Singh <singh01prabhat@gmail.com>
#
# This file is part of BCF.
#
# BCF is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# BCF is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with BCF. If not, see <http://www.gnu.org/licenses/>.
"""BCF XML v3 handler."""
+172 -184
View File
@@ -1,4 +1,3 @@
# BCF - BCF Python library
# Copyright (C) 2021 Prabhat Singh <singh01prabhat@gmail.com>
#
@@ -17,25 +16,27 @@
# You should have received a copy of the GNU Lesser General Public License
# along with BCF. If not, see <http://www.gnu.org/licenses/>.
import uuid
import time
import json
import urllib
import requests
import webbrowser
import http.server
import base64
import tempfile
import http.server
import os
import tempfile
import time
import urllib
import uuid
import webbrowser
from re import A
from typing import Any, Optional, Tuple
import requests
client_id, client_secret = "", ""
class OAuthReceiver(http.server.BaseHTTPRequestHandler):
def do_GET(self):
def do_GET(self) -> None:
query = urllib.parse.parse_qs(urllib.parse.urlparse(self.path).query)
self.server.auth_code = query.get("code", [""])[0]
self.server.auth_state = query.get("state", [""])[0]
self.server.auth_code = query.get("code", [""])[0] # type:ignore
self.server.auth_state = query.get("state", [""])[0] # type:ignore
self.send_response(200)
self.send_header("Content-type", "text/plain")
self.end_headers()
@@ -43,20 +44,21 @@ class OAuthReceiver(http.server.BaseHTTPRequestHandler):
class FoundationClient:
def __init__(self, client_id, client_secret, base_url=None, redirect_subdir=None):
def __init__(
self, client_id: str, client_secret: str, base_url: Optional[str] = None, redirect_subdir: Optional[str] = None
) -> None:
self.baseurl = base_url
self.access_token = ""
self.refresh_token = ""
self.access_token_expires_on = time.time()
self.refresh_token_expires_on = float("inf")
self.auth_endpoint = None
self.token_endpoint = None
self.token_endpoint = ""
self.client_id = client_id
self.client_secret = client_secret
self.auth_method = None
self.auth_method: Optional[str] = None
self.redirect_subdir = redirect_subdir
def get_access_token(self):
def get_access_token(self) -> str:
if self.access_token and self.access_token_expires_on > time.time():
return self.access_token
elif self.refresh_token and self.refresh_token_expires_on > time.time():
@@ -65,18 +67,18 @@ class FoundationClient:
self.login()
return self.access_token
def get_auth_methods(self):
def get_auth_methods(self) -> list[Any]:
resp = requests.get(f"{self.baseurl}foundation/1.0/auth")
return resp.json()["supported_oauth2_flows"]
def get_versions(self):
def get_versions(self) -> list[Any]:
resp = requests.get(f"{self.baseurl}foundation/versions")
return resp.json()["versions"]
def login(self):
def login(self) -> None:
resp = requests.get(f"{self.baseurl}foundation/1.0/auth")
values = resp.json()
self.auth_endpoint = values["oauth2_auth_url"]
auth_endpoint = values["oauth2_auth_url"]
self.token_endpoint = values["oauth2_token_url"]
with http.server.HTTPServer(("", 8080), OAuthReceiver) as server:
@@ -89,25 +91,22 @@ class FoundationClient:
"redirect_uri": f"http://localhost:{server.server_address[1]}/{self.redirect_subdir}",
}
)
if "?" in self.auth_endpoint:
webbrowser.open(f"{self.auth_endpoint}&{query}")
if "?" in auth_endpoint:
webbrowser.open(f"{auth_endpoint}&{query}")
else:
webbrowser.open(f"{self.auth_endpoint}?{query}")
webbrowser.open(f"{auth_endpoint}?{query}")
server.timeout = 100
server.state = state
server.handle_request()
if server.auth_code and server.auth_state == state:
if server.auth_code and server.auth_state == state: # pylint: disable=E1101
data = {
"grant_type": "authorization_code",
"code": server.auth_code,
"code": server.auth_code, # pylint: disable=E1101 type:ignore
"redirect_uri": f"http://localhost:{server.server_address[1]}/{self.redirect_subdir}",
}
auth_string = f"{self.client_id}:{self.client_secret}"
header_string = base64.b64encode(auth_string.encode("utf-8")).decode("utf-8")
headers = {"Authorization": f"Basic {header_string}"}
headers = self._get_access_token_headers()
self.set_tokens_from_response(requests.post(self.token_endpoint, data=data, headers=headers))
def get_refresh_token(self):
def get_refresh_token(self) -> None:
self.set_tokens_from_response(
requests.post(
self.token_endpoint,
@@ -118,10 +117,8 @@ class FoundationClient:
).json()
)
def get_new_access_token(self):
auth_string = f"{self.client_id}:{self.client_secret}"
header_string = base64.b64encode(auth_string.encode("utf-8")).decode("utf-8")
headers = {"Authorization": f"Basic {header_string}"}
def get_new_access_token(self) -> None:
headers = self._get_access_token_headers()
self.set_tokens_from_response(
requests.post(
self.token_endpoint,
@@ -133,35 +130,41 @@ class FoundationClient:
).json()
)
def set_auth_method(self, method="authorization_code_grant"):
def _get_access_token_headers(self) -> dict[str, str]:
auth_string = f"{self.client_id}:{self.client_secret}"
header_string = base64.b64encode(auth_string.encode("utf-8")).decode("utf-8")
return {"Authorization": f"Basic {header_string}"}
def set_auth_method(self, method: str = "authorization_code_grant") -> None:
if method != "authorization_code_grant":
raise NotImplementedError(f"{method} not supported")
else:
self.auth_method = method
def set_tokens_from_response(self, response):
response = response.json()
self.access_token = response["access_token"]
self.refresh_token = response["refresh_token"]
self.access_token_expires_on = time.time() + response["expires_in"]
if "refresh_token_expires_in" in response:
self.refresh_token_expires_on = time.time() + response["refresh_token_expires_in"]
def set_tokens_from_response(self, response: requests.Response) -> None:
response_dict = response.json()
self.access_token = response_dict["access_token"]
self.refresh_token = response_dict["refresh_token"]
self.access_token_expires_on = time.time() + response_dict["expires_in"]
if "refresh_token_expires_in" in response_dict:
self.refresh_token_expires_on = time.time() + response_dict["refresh_token_expires_in"]
class BcfClient:
def __init__(self, foundation_client):
def __init__(self, foundation_client: FoundationClient) -> None:
self.foundation_client = foundation_client
self.version_id = None
self.baseurl = None
self.version_id: Optional[str] = None
self.baseurl: Optional[str] = None
self.filepath = tempfile.mkdtemp()
def set_version(self, version):
def set_version(self, version: dict[str, str]) -> None:
self.version_id = version["version_id"]
self.baseurl = version["api_base_url"]
def get(self, endpoint, params=None, is_auth_required=False):
def get(self, endpoint: str, params: Any = None, is_auth_required: bool = False) -> Any:
# TODO: handle error http status codes and raise exception. Follow error.json standard.
headers = {"Authorization": "Bearer " + self.foundation_client.get_access_token()}
headers = {"Authorization": f"Bearer {self.foundation_client.get_access_token()}"}
response = requests.get(f"{self.baseurl}{endpoint}", headers=headers, params=params or None)
try:
response = requests.get(f"{self.baseurl}{endpoint}", headers=headers, params=params or None)
@@ -171,68 +174,62 @@ class BcfClient:
except requests.exceptions.HTTPError as e:
print(f"message: {response.reason}' '{response.status_code}' '{ e }")
def post(self, endpoint, data=None, params=None):
def post(self, endpoint: str, data: Any = None, params: Any = None) -> Tuple[int, str]:
headers = {
"Authorization": "Bearer " + self.foundation_client.get_access_token(),
"Authorization": f"Bearer {self.foundation_client.get_access_token()}",
"Content-type": "application/json",
}
try:
response = requests.post(
f"{self.baseurl}{endpoint}",
headers=headers,
params=params or None,
data=data or None,
f"{self.baseurl}{endpoint}", headers=headers, params=params or None, data=data or None
)
if response.status_code == 201:
return response.status_code, response.text
response.raise_for_status()
if response.status_code != 201:
response.raise_for_status()
return response.status_code, response.text
except requests.exceptions.HTTPError as errh:
print(f"message: {response.reason}' '{response.status_code}, {errh}")
return response.status_code, response.reason
def put(self, endpoint, data=None, params=None):
def put(self, endpoint: str, data: Any = None, params: Any = None) -> Tuple[int, str]:
headers = {
"Authorization": "Bearer " + self.foundation_client.get_access_token(),
"Authorization": f"Bearer {self.foundation_client.get_access_token()}",
"Content-type": "application/json",
}
try:
response = requests.put(
f"{self.baseurl}{endpoint}",
headers=headers,
params=params or None,
data=data or None,
f"{self.baseurl}{endpoint}", headers=headers, params=params or None, data=data or None
)
if response.status_code == 200:
return response.status_code, response.text
response.raise_for_status()
if response.status_code != 200:
response.raise_for_status()
return response.status_code, response.text
except requests.exceptions.HTTPError as errh:
print(f"message: {response.reason}' '{response.status_code}, {errh}")
return response.status_code, response.reason
def delete(self, endpoint, params=None):
def delete(self, endpoint: str, params: Any = None) -> Tuple[int, str]:
headers = {
"Authorization": "Bearer " + self.foundation_client.get_access_token(),
"Authorization": f"Bearer {self.foundation_client.get_access_token()}",
"Content-type": "application/json",
}
try:
response = requests.delete(
f"{self.baseurl}{endpoint}",
headers=headers,
params=params or None,
)
if response.status_code == 200:
return response.status_code, response.text
response.raise_for_status()
response = requests.delete(f"{self.baseurl}{endpoint}", headers=headers, params=params or None)
if response.status_code != 200:
response.raise_for_status()
return response.status_code, response.text
except requests.exceptions.HTTPError as errh:
print(f"message: {response.reason}' '{response.status_code}, {errh}")
return response.status_code, response.reason
def get_projects(self) -> list:
return self.get(
f"/projects",
)
def get_projects(self) -> list[Any]:
return self.get("/projects")
def get_project(
self,
project_id="",
) -> dict:
def get_project(self, project_id: str = "") -> dict[str, Any]:
return self.get(
f"/projects/{project_id}",
{
@@ -240,16 +237,13 @@ class BcfClient:
},
)
def update_project(self, project_id="", data=None) -> dict:
def update_project(self, project_id: str = "", data: Any = None) -> Tuple[int, str]:
url = f"{self.baseurl}/projects/{project_id}"
headers = {"Authorization": "Bearer " + self.foundation_client.get_access_token()}
headers = {"Authorization": f"Bearer {self.foundation_client.get_access_token()}"}
resp = requests.put(url, headers=headers, data=data)
return resp.status_code, resp.text
def get_extensions(
self,
project_id="",
) -> dict:
def get_extensions(self, project_id: str = "") -> dict[str, Any]:
return self.get(
f"/projects/{project_id}/extensions",
{
@@ -259,10 +253,10 @@ class BcfClient:
def get_topics(
self,
project_id="",
topics="",
query_string=None,
) -> list:
project_id: str = "",
topics: str = "",
query_string: Optional[str] = None,
) -> list[Any]:
# return self.get(
# f"/projects/{project_id}/topics",
# {
@@ -273,7 +267,7 @@ class BcfClient:
# )
pass
def get_topic(self, project_id="", topic_id="") -> dict:
def get_topic(self, project_id: str = "", topic_id: str = "") -> dict[str, Any]:
return self.get(
f"/projects/{project_id}/topics/{topic_id}",
{
@@ -282,42 +276,40 @@ class BcfClient:
},
)
def create_topic(self, project_id="", data=None):
def create_topic(self, project_id: str = "", data: Any = None) -> Tuple[int, str]:
return self.post(f"/projects/{project_id}/topics", data=data)
def update_topic(self, project_id="", topic_id="", data=None) -> dict:
def update_topic(self, project_id: str = "", topic_id: str = "", data: Any = None) -> Tuple[int, str]:
return self.put(f"/projects/{project_id}/topics/{topic_id}", data=data)
def delete_topic(self, project_id="", topic_id=""):
def delete_topic(self, project_id: str = "", topic_id: str = "") -> Tuple[int, str]:
return self.delete(f"/projects/{project_id}/topics/{topic_id}")
def get_snippet(self, project_id="", topic_id="") -> str:
def get_snippet(self, project_id: str = "", topic_id: str = "") -> Tuple[int, str]:
headers = {
"Authorization": "Bearer " + self.foundation_client.get_access_token(),
"Authorization": f"Bearer {self.foundation_client.get_access_token()}",
"Content-type": "application/octet-stream",
}
response = requests.get(
f"{self.baseurl}/projects/{project_id}/topics/{topic_id}/snippet",
headers=headers,
)
# TODO: write to tmpdir
with open(os.path.join(self.filepath, f"{project_id}_{topic_id}_snippet.txt"), "wb") as f:
f.write(response.content.decode("utf-8"))
return response.status_code, response.content
def update_snippet(self, project_id="", topic_id="", files=None, data=None):
response = requests.get(f"{self.baseurl}/projects/{project_id}/topics/{topic_id}/snippet", headers=headers)
content = response.content.decode("utf-8")
with open(os.path.join(self.filepath, f"{project_id}_{topic_id}_snippet.txt"), "w") as f:
f.write(content)
return response.status_code, content
def update_snippet(self, project_id: str = "", topic_id: str = "", files: Any = None, data: Any = None) -> int:
headers = {
"Authorization": "Bearer " + self.foundation_client.get_access_token(),
"Authorization": f"Bearer {self.foundation_client.get_access_token()}",
"Content-type": "application/octet-stream",
}
response = requests.put(
f"{self.baseurl}/projects/{project_id}/topics/{topic_id}/snippet",
headers=headers,
files=files,
f"{self.baseurl}/projects/{project_id}/topics/{topic_id}/snippet", headers=headers, files=files
)
return response.status_code
def get_files_information(self, project_id="") -> list:
def get_files_information(self, project_id: str = "") -> list[Any]:
return self.get(
f"/projects/{project_id}/files_information",
{
@@ -325,7 +317,7 @@ class BcfClient:
},
)
def get_files(self, project_id="", topic_id="") -> list:
def get_files(self, project_id: str = "", topic_id: str = "") -> list[Any]:
return self.get(
f"/projects/{project_id}/topics/{topic_id}/files",
{
@@ -336,32 +328,32 @@ class BcfClient:
def update_files(
self,
project_id="",
topic_id="",
data=None,
params=None,
):
project_id: str = "",
topic_id: str = "",
data: Any = None,
params: Any = None,
) -> Tuple[int, str]:
return self.put(
f"/projects/{project_id}/topics/{topic_id}/files",
data=data,
)
def get_comments(self, project_id="", topic_id="") -> list:
def get_comments(self, project_id: str = "", topic_id: str = "") -> None:
pass
def create_comments(
self,
project_id="",
topic_id="",
data=None,
params=None,
):
project_id: str = "",
topic_id: str = "",
data: Any = None,
params: Any = None,
) -> Tuple[int, str]:
return self.post(
f"/projects/{project_id}/topics/{topic_id}/comments",
data=data,
)
def get_comment(self, project_id="", topic_id="", comment_id="") -> dict:
def get_comment(self, project_id: str = "", topic_id: str = "", comment_id: str = "") -> dict[str, Any]:
return self.get(
f"/projects/{project_id}/topics/{topic_id}/comments/{comment_id}",
{
@@ -371,22 +363,22 @@ class BcfClient:
},
)
def delete_comment(self, project_id="", topic_id="", comment_id=""):
def delete_comment(self, project_id: str = "", topic_id: str = "", comment_id: str = "") -> Tuple[int, str]:
return self.delete(f"/projects/{project_id}/topics/{topic_id}/comments/{comment_id}")
def update_comment(
self,
project_id="",
topic_id="",
comment_id="",
data=None,
):
project_id: str = "",
topic_id: str = "",
comment_id: str = "",
data: Any = None,
) -> Tuple[int, str]:
return self.put(
f"/projects/{project_id}/topics/{topic_id}/comments/{comment_id}",
data=data,
)
def get_viewpoints(self, project_id="", topic_id="") -> list:
def get_viewpoints(self, project_id: str = "", topic_id: str = "") -> list[Any]:
return self.get(
f"/projects/{project_id}/topics/{topic_id}/viewpoints",
{
@@ -395,13 +387,13 @@ class BcfClient:
},
)
def create_viewpoints(self, project_id="", topic_id="", data=None):
def create_viewpoints(self, project_id: str = "", topic_id: str = "", data: Any = None) -> Tuple[int, str]:
return self.post(
f"/projects/{project_id}/topics/{topic_id}/viewpoints",
data=data,
)
def get_viewpoint(self, project_id="", topic_id="", viewpoint_id="") -> dict:
def get_viewpoint(self, project_id: str = "", topic_id: str = "", viewpoint_id: str = "") -> dict[str, Any]:
return self.get(
f"/projects/{project_id}/topics/{topic_id}/viewpoints/{viewpoint_id}",
{
@@ -413,15 +405,15 @@ class BcfClient:
def delete_viewpoint(
self,
project_id="",
topic_id="",
viewpoint_id="",
):
project_id: str = "",
topic_id: str = "",
viewpoint_id: str = "",
) -> Tuple[int, str]:
return self.delete(
f"/projects/{project_id}/topics/{topic_id}/viewpoints/{viewpoint_id}",
)
def get_snapshot(self, project_id="", topic_id="", viewpoint_id="") -> str:
def get_snapshot(self, project_id: str = "", topic_id: str = "", viewpoint_id: str = "") -> str:
return self.get(
f"/projects/{project_id}/topics/{topic_id}/viewpoints/{viewpoint_id}/snapshot",
{
@@ -431,7 +423,7 @@ class BcfClient:
},
)
def get_bitmap(self, project_id="", topic_id="", viewpoint_id="", bitmap_id="") -> str:
def get_bitmap(self, project_id: str = "", topic_id: str = "", viewpoint_id: str = "", bitmap_id: str = "") -> str:
return self.get(
f"/projects/{project_id}/topics/{topic_id}/viewpoints/{viewpoint_id}/bitmaps/{bitmap_id}",
{
@@ -442,7 +434,7 @@ class BcfClient:
},
)
def get_selection(self, project_id="", topic_id="", viewpoint_id="") -> dict:
def get_selection(self, project_id: str = "", topic_id: str = "", viewpoint_id: str = "") -> dict[str, Any]:
return self.get(
f"/projects/{project_id}/topics/{topic_id}/viewpoints/{viewpoint_id}/selection",
{
@@ -452,7 +444,7 @@ class BcfClient:
},
)
def get_coloring(self, project_id="", topic_id="", viewpoint_id="") -> dict:
def get_coloring(self, project_id: str = "", topic_id: str = "", viewpoint_id: str = "") -> dict[str, Any]:
return self.get(
f"/projects/{project_id}/topics/{topic_id}/viewpoints/{viewpoint_id}/coloring",
{
@@ -462,7 +454,7 @@ class BcfClient:
},
)
def get_visibility(self, project_id="", topic_id="", viewpoint_id="") -> dict:
def get_visibility(self, project_id: str = "", topic_id: str = "", viewpoint_id: str = "") -> dict[str, Any]:
return self.get(
f"/projects/{project_id}/topics/{topic_id}/viewpoints/{viewpoint_id}/visibility",
{
@@ -472,7 +464,7 @@ class BcfClient:
},
)
def get_related_topics(self, project_id="", topic_id="") -> list:
def get_related_topics(self, project_id: str = "", topic_id: str = "") -> list[Any]:
return self.get(
f"/projects/{project_id}/topics/{topic_id}/related_topics",
{
@@ -483,16 +475,16 @@ class BcfClient:
def update_related_topics(
self,
project_id="",
topic_id="",
data=None,
):
project_id: str = "",
topic_id: str = "",
data: Any = None,
) -> Tuple[int, str]:
return self.put(
f"/projects/{project_id}/topics/{topic_id}/related_topics",
data=data,
)
def get_document_references(self, project_id="", topic_id="") -> list:
def get_document_references(self, project_id: str = "", topic_id: str = "") -> list[Any]:
return self.get(
f"/projects/{project_id}/topics/{topic_id}/document_references",
{
@@ -503,10 +495,10 @@ class BcfClient:
def create_document_reference(
self,
project_id="",
topic_id="",
data=None,
):
project_id: str = "",
topic_id: str = "",
data: Any = None,
) -> Tuple[int, str]:
return self.post(
f"/projects/{project_id}/topics/{topic_id}/document_references",
data=data,
@@ -514,17 +506,17 @@ class BcfClient:
def update_document_references(
self,
project_id="",
topic_id="",
document_reference_id="",
data=None,
):
project_id: str = "",
topic_id: str = "",
document_reference_id: str = "",
data: Any = None,
) -> Tuple[int, str]:
return self.put(
f"/projects/{project_id}/topics/{topic_id}/document_references/{document_reference_id}",
data=data,
)
def get_documents(self, project_id="", topic_id="") -> list:
def get_documents(self, project_id: str = "", topic_id: str = "") -> list[Any]:
return self.get(
f"/projects/{project_id}/topics/{topic_id}/documents",
{
@@ -534,40 +526,36 @@ class BcfClient:
)
def create_document(
self,
project_id="",
topic_id="",
guid=None,
files=None,
data=None,
):
self, project_id: str = "", topic_id: str = "", guid: Optional[str] = None, files: Any = None, data: Any = None
) -> int:
headers = {
"Authorization": "Bearer " + self.foundation_client.get_access_token(),
"Authorization": f"Bearer {self.foundation_client.get_access_token()}",
"Content-type": "application/octet-stream",
}
response = requests.post(
f"/projects/{project_id}/topics/{topic_id}/documents",
data=data,
params={guid},
params={"guid": guid},
files=files,
headers=headers,
)
return response.status_code
def get_document(self, project_id="", topic_id="", document_id="") -> str:
def get_document(self, project_id: str = "", topic_id: str = "", document_id: str = "") -> Tuple[int, str]:
headers = {
"Authorization": "Bearer " + self.foundation_client.get_access_token(),
"Authorization": f"Bearer {self.foundation_client.get_access_token()}",
"Content-type": "application/octet-stream",
}
response = requests.get(
f"{self.baseurl}/projects/{project_id}/topics/documents/{document_id}",
headers=headers,
)
with open(os.path.join(self.filepath, f"{project_id}_{topic_id}_{document_id}_document.txt"), "wb") as f:
f.write(response.content.decode("utf-8"))
return response.status_code, response.content
def get_topics_events(self, project_id="") -> list:
response = requests.get(f"{self.baseurl}/projects/{project_id}/topics/documents/{document_id}", headers=headers)
content = response.content.decode("utf-8")
with open(os.path.join(self.filepath, f"{project_id}_{topic_id}_{document_id}_document.txt"), "w") as f:
f.write(content)
return response.status_code, content
def get_topics_events(self, project_id: str = "") -> list[Any]:
return self.get(
f"/projects/{project_id}/topics/events",
{
@@ -575,7 +563,7 @@ class BcfClient:
},
)
def get_topic_events(self, project_id="", topic_id="") -> list:
def get_topic_events(self, project_id: str = "", topic_id: str = "") -> list[Any]:
return self.get(
f"/projects/{project_id}/topics/{topic_id}/events",
{
@@ -584,7 +572,7 @@ class BcfClient:
},
)
def get_comments_events(self, project_id="") -> list:
def get_comments_events(self, project_id: str = "") -> list[Any]:
return self.get(
f"/projects/{project_id}/topics/comments/events",
{
@@ -592,7 +580,7 @@ class BcfClient:
},
)
def get_comment_events(self, project_id="", topic_id="", comment_id="") -> list:
def get_comment_events(self, project_id: str = "", topic_id: str = "", comment_id: str = "") -> list[Any]:
return self.get(
f"/projects/{project_id}/topics/{topic_id}/comments/{comment_id}/events",
{
File diff suppressed because it is too large Load Diff
-200
View File
@@ -1,200 +0,0 @@
# BCF - BCF Python library
# Copyright (C) 2021 Prabhat Singh <singh01prabhat@gmail.com>
#
# This file is part of BCF.
#
# BCF is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# BCF is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with BCF. If not, see <http://www.gnu.org/licenses/>.
class Project:
def __init__(self):
self.project_id = ""
self.name = ""
class BimSnippet:
def __init__(self):
self.snippet_type = None
self.is_external = False
self.reference = None
self.reference_schema = None
class DocumentReference:
def __init__(self):
self.description = None
self.document_guid = None
self.url = None
self.guid = None
class RelatedTopic:
def __init__(self):
self.guid = None
class HeaderFile:
def __init__(self):
self.filename = ""
self.date = None
self.reference = ""
self.ifc_project = None
self.ifc_spatial_structure_element = None
self.is_external = True
class Header:
def __init__(self):
self.files = []
class Topic:
def __init__(self):
self.reference_links = []
self.title = ""
self.priority = None
self.index = None # Deprecated, stored, but ignored
self.labels = []
self.creation_date = None
self.creation_author = None
self.modified_date = None
self.modified_author = None
self.due_date = None
self.assigned_to = None
self.stage = None
self.description = None
self.bim_snippet = None
self.document_references = []
self.related_topics = []
self.topic_status = None
self.topic_type = None
self.guid = None
self.header = None
self.comments = {}
self.viewpoints = {}
self.server_assigned_id = ""
class Comment:
def __init__(self):
self.guid = None
self.date = None
self.author = ""
self.comment = ""
self.viewpoint = None
self.modified_date = None
self.modified_author = ""
class ViewSetupHints:
def __init__(self):
self.spaces_visible = False
self.space_boundaries_visible = False
self.openings_visible = False
class Component:
def __init__(self):
self.originating_system = None
self.authoring_tool_id = None
self.ifc_guid = None
class ComponentVisibility:
def __init__(self):
self.exceptions = []
self.default_visibility = False
self.view_setup_hints = None
class Color:
def __init__(self):
self.color = None
self.components = []
class Components:
def __init__(self):
self.selection = []
self.visibility = None
self.coloring = []
class Point:
def __init__(self):
self.x = 0
self.y = 0
self.z = 0
class Direction(Point):
pass
class OrthogonalCamera:
def __init__(self):
self.camera_view_point = Point()
self.camera_direction = Direction()
self.camera_up_vector = Direction()
self.view_to_world_scale = 1.0
self.aspect_ratio = 1.0
class PerspectiveCamera:
def __init__(self):
self.camera_view_point = Point()
self.camera_direction = Direction()
self.camera_up_vector = Direction()
self.field_of_view = 60.0
self.aspect_ratio = 1.0
class Line:
def __init__(self):
self.start_point = Point()
self.end_point = Point()
class ClippingPlane:
def __init__(self):
self.location = Point()
self.direction = Direction()
class Bitmap:
def __init__(self):
self.reference = "" # Only in BCF-XML
self.bitmap_data = None # Only in BCF-API
self.bitmap_format = "PNG" # Enum of png or jpg
self.location = Point()
self.normal = Direction()
self.up = Direction()
self.height = 1.0
class Viewpoint:
def __init__(self):
self.guid = None
self.viewpoint = None
self.snapshot = None
self.index = None
self.components = None # It's not a list, despite the plural name
self.orthogonal_camera = None
self.perspective_camera = None
self.lines = []
self.clipping_planes = []
self.bitmaps = []
+61
View File
@@ -0,0 +1,61 @@
"""BCF XML V3 Documents handler."""
import zipfile
from typing import Any, Optional
import bcf.v3.model as mdl
from bcf.inmemory_zipfile import ZipFileInterface
from bcf.xml_parser import AbstractXmlParserSerializer, XmlParserSerializer
class DocumentsHandler:
"""BCF documents handler."""
def __init__(
self,
definition: mdl.DocumentInfo,
documents: Optional[dict[str, bytes]] = None,
xml_handler: Optional[AbstractXmlParserSerializer] = None,
) -> None:
self.definition = definition
self.documents = documents or {}
self._xml_handler = xml_handler or XmlParserSerializer()
@classmethod
def load(
cls,
zip_file: zipfile.ZipFile,
xml_handler: Optional[AbstractXmlParserSerializer] = None,
) -> Optional["DocumentsHandler"]:
"""
Loads the documents from the given zip file directory.
Args:
zip_path: The directory path inside the zip file.
xml_handler: The xml parser/serializer to use.
Returns:
The documents handler.
"""
xml_handler = xml_handler or XmlParserSerializer()
file_to_open = zipfile.Path(zip_file, "documents.xml")
if not file_to_open.exists():
return None
definition = xml_handler.parse(file_to_open.read_bytes(), mdl.DocumentInfo)
documents = {}
if def_docs := definition.documents:
for document in def_docs.document:
document_path = zipfile.Path(zip_file, f"documents/{document.guid}")
if document_path.exists():
documents[document.filename] = document_path.read_bytes()
return cls(definition, documents=documents)
def save(self, bcf_zip: ZipFileInterface) -> None:
"""Save the documents to the zip file."""
bcf_zip.writestr("documents.xml", self._xml_handler.serialize(self.definition))
if documents := self.definition.documents:
for doc in documents.document:
if doc.filename in self.documents:
bcf_zip.writestr(
f"documents/{doc.guid}",
self.documents[doc.filename],
)
+110
View File
@@ -0,0 +1,110 @@
from bcf.v3.model.documents import Document, DocumentInfo, DocumentInfoDocuments
from bcf.v3.model.extensions import (
Extensions,
ExtensionsPriorities,
ExtensionsSnippetTypes,
ExtensionsStages,
ExtensionsTopicLabels,
ExtensionsTopicStatuses,
ExtensionsTopicTypes,
ExtensionsUsers,
)
from bcf.v3.model.markup import (
BimSnippet,
Comment,
CommentViewpoint,
DocumentReference,
File,
Header,
HeaderFiles,
Markup,
Topic,
TopicComments,
TopicDocumentReferences,
TopicLabels,
TopicReferenceLinks,
TopicRelatedTopics,
TopicRelatedTopicsRelatedTopic,
TopicViewpoints,
ViewPoint,
)
from bcf.v3.model.project import Project, ProjectInfo
from bcf.v3.model.version import Version
from bcf.v3.model.visinfo import (
Bitmap,
BitmapFormat,
ClippingPlane,
Component,
ComponentColoring,
ComponentColoringColor,
ComponentColoringColorComponents,
Components,
ComponentSelection,
ComponentVisibility,
ComponentVisibilityExceptions,
Direction,
Line,
OrthogonalCamera,
PerspectiveCamera,
Point,
ViewSetupHints,
VisualizationInfo,
VisualizationInfoBitmaps,
VisualizationInfoClippingPlanes,
VisualizationInfoLines,
)
__all__ = [
"Document",
"DocumentInfo",
"DocumentInfoDocuments",
"Extensions",
"ExtensionsPriorities",
"ExtensionsSnippetTypes",
"ExtensionsStages",
"ExtensionsTopicLabels",
"ExtensionsTopicStatuses",
"ExtensionsTopicTypes",
"ExtensionsUsers",
"BimSnippet",
"Comment",
"CommentViewpoint",
"DocumentReference",
"File",
"Header",
"HeaderFiles",
"Markup",
"Topic",
"TopicComments",
"TopicDocumentReferences",
"TopicLabels",
"TopicReferenceLinks",
"TopicRelatedTopics",
"TopicRelatedTopicsRelatedTopic",
"TopicViewpoints",
"ViewPoint",
"Project",
"ProjectInfo",
"Version",
"Bitmap",
"BitmapFormat",
"ClippingPlane",
"Component",
"ComponentColoring",
"ComponentColoringColor",
"ComponentColoringColorComponents",
"ComponentSelection",
"ComponentVisibility",
"ComponentVisibilityExceptions",
"Components",
"Direction",
"Line",
"OrthogonalCamera",
"PerspectiveCamera",
"Point",
"ViewSetupHints",
"VisualizationInfo",
"VisualizationInfoBitmaps",
"VisualizationInfoClippingPlanes",
"VisualizationInfoLines",
]
+61
View File
@@ -0,0 +1,61 @@
from dataclasses import dataclass, field
from typing import List, Optional
@dataclass(slots=True, kw_only=True)
class Document:
filename: str = field(
metadata={
"name": "Filename",
"type": "Element",
"namespace": "",
"required": True,
"min_length": 1,
"white_space": "collapse",
}
)
description: Optional[str] = field(
default=None,
metadata={
"name": "Description",
"type": "Element",
"namespace": "",
"min_length": 1,
"white_space": "collapse",
}
)
guid: str = field(
metadata={
"name": "Guid",
"type": "Attribute",
"required": True,
"pattern": r"[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}",
}
)
@dataclass(slots=True, kw_only=True)
class DocumentInfoDocuments:
class Meta:
global_type = False
document: List[Document] = field(
default_factory=list,
metadata={
"name": "Document",
"type": "Element",
"namespace": "",
}
)
@dataclass(slots=True, kw_only=True)
class DocumentInfo:
documents: Optional[DocumentInfoDocuments] = field(
default=None,
metadata={
"name": "Documents",
"type": "Element",
"namespace": "",
}
)
+181
View File
@@ -0,0 +1,181 @@
from dataclasses import dataclass, field
from typing import List, Optional
@dataclass(slots=True, kw_only=True)
class ExtensionsPriorities:
class Meta:
global_type = False
priority: List[str] = field(
default_factory=list,
metadata={
"name": "Priority",
"type": "Element",
"namespace": "",
"min_length": 1,
"white_space": "collapse",
}
)
@dataclass(slots=True, kw_only=True)
class ExtensionsSnippetTypes:
class Meta:
global_type = False
snippet_type: List[str] = field(
default_factory=list,
metadata={
"name": "SnippetType",
"type": "Element",
"namespace": "",
"min_length": 1,
"white_space": "collapse",
}
)
@dataclass(slots=True, kw_only=True)
class ExtensionsStages:
class Meta:
global_type = False
stage: List[str] = field(
default_factory=list,
metadata={
"name": "Stage",
"type": "Element",
"namespace": "",
"min_length": 1,
"white_space": "collapse",
}
)
@dataclass(slots=True, kw_only=True)
class ExtensionsTopicLabels:
class Meta:
global_type = False
topic_label: List[str] = field(
default_factory=list,
metadata={
"name": "TopicLabel",
"type": "Element",
"namespace": "",
"min_length": 1,
"white_space": "collapse",
}
)
@dataclass(slots=True, kw_only=True)
class ExtensionsTopicStatuses:
class Meta:
global_type = False
topic_status: List[str] = field(
default_factory=list,
metadata={
"name": "TopicStatus",
"type": "Element",
"namespace": "",
"min_length": 1,
"white_space": "collapse",
}
)
@dataclass(slots=True, kw_only=True)
class ExtensionsTopicTypes:
class Meta:
global_type = False
topic_type: List[str] = field(
default_factory=list,
metadata={
"name": "TopicType",
"type": "Element",
"namespace": "",
"min_length": 1,
"white_space": "collapse",
}
)
@dataclass(slots=True, kw_only=True)
class ExtensionsUsers:
class Meta:
global_type = False
user: List[str] = field(
default_factory=list,
metadata={
"name": "User",
"type": "Element",
"namespace": "",
"min_length": 1,
"white_space": "collapse",
}
)
@dataclass(slots=True, kw_only=True)
class Extensions:
topic_types: Optional[ExtensionsTopicTypes] = field(
default=None,
metadata={
"name": "TopicTypes",
"type": "Element",
"namespace": "",
}
)
topic_statuses: Optional[ExtensionsTopicStatuses] = field(
default=None,
metadata={
"name": "TopicStatuses",
"type": "Element",
"namespace": "",
}
)
priorities: Optional[ExtensionsPriorities] = field(
default=None,
metadata={
"name": "Priorities",
"type": "Element",
"namespace": "",
}
)
topic_labels: Optional[ExtensionsTopicLabels] = field(
default=None,
metadata={
"name": "TopicLabels",
"type": "Element",
"namespace": "",
}
)
users: Optional[ExtensionsUsers] = field(
default=None,
metadata={
"name": "Users",
"type": "Element",
"namespace": "",
}
)
snippet_types: Optional[ExtensionsSnippetTypes] = field(
default=None,
metadata={
"name": "SnippetTypes",
"type": "Element",
"namespace": "",
}
)
stages: Optional[ExtensionsStages] = field(
default=None,
metadata={
"name": "Stages",
"type": "Element",
"namespace": "",
}
)
+616
View File
@@ -0,0 +1,616 @@
from dataclasses import dataclass, field
from typing import List, Optional
from xsdata.models.datatype import XmlDateTime
@dataclass(slots=True, kw_only=True)
class BimSnippet:
reference: str = field(
metadata={
"name": "Reference",
"type": "Element",
"namespace": "",
"required": True,
"min_length": 1,
"white_space": "collapse",
}
)
reference_schema: str = field(
metadata={
"name": "ReferenceSchema",
"type": "Element",
"namespace": "",
"required": True,
"min_length": 1,
"white_space": "collapse",
}
)
snippet_type: str = field(
metadata={
"name": "SnippetType",
"type": "Attribute",
"required": True,
"min_length": 1,
"white_space": "collapse",
}
)
is_external: bool = field(
default=False,
metadata={
"name": "IsExternal",
"type": "Attribute",
}
)
@dataclass(slots=True, kw_only=True)
class CommentViewpoint:
class Meta:
global_type = False
guid: str = field(
metadata={
"name": "Guid",
"type": "Attribute",
"required": True,
"pattern": r"[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}",
}
)
@dataclass(slots=True, kw_only=True)
class DocumentReference:
document_guid: Optional[str] = field(
default=None,
metadata={
"name": "DocumentGuid",
"type": "Element",
"namespace": "",
"pattern": r"[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}",
}
)
url: Optional[str] = field(
default=None,
metadata={
"name": "Url",
"type": "Element",
"namespace": "",
"min_length": 1,
"white_space": "collapse",
}
)
description: Optional[str] = field(
default=None,
metadata={
"name": "Description",
"type": "Element",
"namespace": "",
"min_length": 1,
"white_space": "collapse",
}
)
guid: str = field(
metadata={
"name": "Guid",
"type": "Attribute",
"required": True,
"pattern": r"[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}",
}
)
@dataclass(slots=True, kw_only=True)
class File:
filename: Optional[str] = field(
default=None,
metadata={
"name": "Filename",
"type": "Element",
"namespace": "",
"min_length": 1,
"white_space": "collapse",
}
)
date: Optional[XmlDateTime] = field(
default=None,
metadata={
"name": "Date",
"type": "Element",
"namespace": "",
}
)
reference: Optional[str] = field(
default=None,
metadata={
"name": "Reference",
"type": "Element",
"namespace": "",
"min_length": 1,
"white_space": "collapse",
}
)
ifc_project: Optional[str] = field(
default=None,
metadata={
"name": "IfcProject",
"type": "Attribute",
"length": 22,
"pattern": r"[0-9A-Za-z_$]*",
}
)
ifc_spatial_structure_element: Optional[str] = field(
default=None,
metadata={
"name": "IfcSpatialStructureElement",
"type": "Attribute",
"length": 22,
"pattern": r"[0-9A-Za-z_$]*",
}
)
is_external: bool = field(
default=True,
metadata={
"name": "IsExternal",
"type": "Attribute",
}
)
@dataclass(slots=True, kw_only=True)
class TopicLabels:
class Meta:
global_type = False
label: List[str] = field(
default_factory=list,
metadata={
"name": "Label",
"type": "Element",
"namespace": "",
"min_length": 1,
"white_space": "collapse",
}
)
@dataclass(slots=True, kw_only=True)
class TopicReferenceLinks:
class Meta:
global_type = False
reference_link: List[str] = field(
default_factory=list,
metadata={
"name": "ReferenceLink",
"type": "Element",
"namespace": "",
"min_length": 1,
"white_space": "collapse",
}
)
@dataclass(slots=True, kw_only=True)
class TopicRelatedTopicsRelatedTopic:
class Meta:
global_type = False
guid: str = field(
metadata={
"name": "Guid",
"type": "Attribute",
"required": True,
"pattern": r"[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}",
}
)
@dataclass(slots=True, kw_only=True)
class ViewPoint:
viewpoint: Optional[str] = field(
default=None,
metadata={
"name": "Viewpoint",
"type": "Element",
"namespace": "",
"min_length": 1,
"white_space": "collapse",
}
)
snapshot: Optional[str] = field(
default=None,
metadata={
"name": "Snapshot",
"type": "Element",
"namespace": "",
"min_length": 1,
"white_space": "collapse",
}
)
index: Optional[int] = field(
default=None,
metadata={
"name": "Index",
"type": "Element",
"namespace": "",
}
)
guid: str = field(
metadata={
"name": "Guid",
"type": "Attribute",
"required": True,
"pattern": r"[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}",
}
)
@dataclass(slots=True, kw_only=True)
class Comment:
date: XmlDateTime = field(
metadata={
"name": "Date",
"type": "Element",
"namespace": "",
"required": True,
}
)
author: str = field(
metadata={
"name": "Author",
"type": "Element",
"namespace": "",
"required": True,
"min_length": 1,
"white_space": "collapse",
}
)
comment: Optional[str] = field(
default=None,
metadata={
"name": "Comment",
"type": "Element",
"namespace": "",
"min_length": 1,
"white_space": "collapse",
}
)
viewpoint: Optional[CommentViewpoint] = field(
default=None,
metadata={
"name": "Viewpoint",
"type": "Element",
"namespace": "",
}
)
modified_date: Optional[XmlDateTime] = field(
default=None,
metadata={
"name": "ModifiedDate",
"type": "Element",
"namespace": "",
}
)
modified_author: Optional[str] = field(
default=None,
metadata={
"name": "ModifiedAuthor",
"type": "Element",
"namespace": "",
"min_length": 1,
"white_space": "collapse",
}
)
guid: str = field(
metadata={
"name": "Guid",
"type": "Attribute",
"required": True,
"pattern": r"[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}",
}
)
@dataclass(slots=True, kw_only=True)
class HeaderFiles:
class Meta:
global_type = False
file: List[File] = field(
default_factory=list,
metadata={
"name": "File",
"type": "Element",
"namespace": "",
}
)
@dataclass(slots=True, kw_only=True)
class TopicDocumentReferences:
class Meta:
global_type = False
document_reference: List[DocumentReference] = field(
default_factory=list,
metadata={
"name": "DocumentReference",
"type": "Element",
"namespace": "",
}
)
@dataclass(slots=True, kw_only=True)
class TopicRelatedTopics:
class Meta:
global_type = False
related_topic: List[TopicRelatedTopicsRelatedTopic] = field(
default_factory=list,
metadata={
"name": "RelatedTopic",
"type": "Element",
"namespace": "",
}
)
@dataclass(slots=True, kw_only=True)
class TopicViewpoints:
class Meta:
global_type = False
view_point: List[ViewPoint] = field(
default_factory=list,
metadata={
"name": "ViewPoint",
"type": "Element",
"namespace": "",
}
)
@dataclass(slots=True, kw_only=True)
class Header:
files: Optional[HeaderFiles] = field(
default=None,
metadata={
"name": "Files",
"type": "Element",
"namespace": "",
}
)
@dataclass(slots=True, kw_only=True)
class TopicComments:
class Meta:
global_type = False
comment: List[Comment] = field(
default_factory=list,
metadata={
"name": "Comment",
"type": "Element",
"namespace": "",
}
)
@dataclass(slots=True, kw_only=True)
class Topic:
reference_links: Optional[TopicReferenceLinks] = field(
default=None,
metadata={
"name": "ReferenceLinks",
"type": "Element",
"namespace": "",
}
)
title: str = field(
metadata={
"name": "Title",
"type": "Element",
"namespace": "",
"required": True,
"min_length": 1,
"white_space": "collapse",
}
)
priority: Optional[str] = field(
default=None,
metadata={
"name": "Priority",
"type": "Element",
"namespace": "",
"min_length": 1,
"white_space": "collapse",
}
)
index: Optional[int] = field(
default=None,
metadata={
"name": "Index",
"type": "Element",
"namespace": "",
}
)
labels: Optional[TopicLabels] = field(
default=None,
metadata={
"name": "Labels",
"type": "Element",
"namespace": "",
}
)
creation_date: XmlDateTime = field(
metadata={
"name": "CreationDate",
"type": "Element",
"namespace": "",
"required": True,
}
)
creation_author: str = field(
metadata={
"name": "CreationAuthor",
"type": "Element",
"namespace": "",
"required": True,
"min_length": 1,
"white_space": "collapse",
}
)
modified_date: Optional[XmlDateTime] = field(
default=None,
metadata={
"name": "ModifiedDate",
"type": "Element",
"namespace": "",
}
)
modified_author: Optional[str] = field(
default=None,
metadata={
"name": "ModifiedAuthor",
"type": "Element",
"namespace": "",
"min_length": 1,
"white_space": "collapse",
}
)
due_date: Optional[XmlDateTime] = field(
default=None,
metadata={
"name": "DueDate",
"type": "Element",
"namespace": "",
}
)
assigned_to: Optional[str] = field(
default=None,
metadata={
"name": "AssignedTo",
"type": "Element",
"namespace": "",
"min_length": 1,
"white_space": "collapse",
}
)
stage: Optional[str] = field(
default=None,
metadata={
"name": "Stage",
"type": "Element",
"namespace": "",
"min_length": 1,
"white_space": "collapse",
}
)
description: Optional[str] = field(
default=None,
metadata={
"name": "Description",
"type": "Element",
"namespace": "",
"min_length": 1,
"white_space": "collapse",
}
)
bim_snippet: Optional[BimSnippet] = field(
default=None,
metadata={
"name": "BimSnippet",
"type": "Element",
"namespace": "",
}
)
document_references: Optional[TopicDocumentReferences] = field(
default=None,
metadata={
"name": "DocumentReferences",
"type": "Element",
"namespace": "",
}
)
related_topics: Optional[TopicRelatedTopics] = field(
default=None,
metadata={
"name": "RelatedTopics",
"type": "Element",
"namespace": "",
}
)
comments: Optional[TopicComments] = field(
default=None,
metadata={
"name": "Comments",
"type": "Element",
"namespace": "",
}
)
viewpoints: Optional[TopicViewpoints] = field(
default=None,
metadata={
"name": "Viewpoints",
"type": "Element",
"namespace": "",
}
)
guid: str = field(
metadata={
"name": "Guid",
"type": "Attribute",
"required": True,
"pattern": r"[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}",
}
)
server_assigned_id: Optional[str] = field(
default=None,
metadata={
"name": "ServerAssignedId",
"type": "Attribute",
"min_length": 1,
"white_space": "collapse",
}
)
topic_type: str = field(
metadata={
"name": "TopicType",
"type": "Attribute",
"required": True,
"min_length": 1,
"white_space": "collapse",
}
)
topic_status: str = field(
metadata={
"name": "TopicStatus",
"type": "Attribute",
"required": True,
"min_length": 1,
"white_space": "collapse",
}
)
@dataclass(slots=True, kw_only=True)
class Markup:
header: Optional[Header] = field(
default=None,
metadata={
"name": "Header",
"type": "Element",
"namespace": "",
}
)
topic: Topic = field(
metadata={
"name": "Topic",
"type": "Element",
"namespace": "",
"required": True,
}
)
+37
View File
@@ -0,0 +1,37 @@
from dataclasses import dataclass, field
from typing import Optional
@dataclass(slots=True, kw_only=True)
class Project:
name: Optional[str] = field(
default=None,
metadata={
"name": "Name",
"type": "Element",
"namespace": "",
"min_length": 1,
"white_space": "collapse",
}
)
project_id: str = field(
metadata={
"name": "ProjectId",
"type": "Attribute",
"required": True,
"min_length": 1,
"white_space": "collapse",
}
)
@dataclass(slots=True, kw_only=True)
class ProjectInfo:
project: Project = field(
metadata={
"name": "Project",
"type": "Element",
"namespace": "",
"required": True,
}
)
+12
View File
@@ -0,0 +1,12 @@
from dataclasses import dataclass, field
@dataclass(slots=True, kw_only=True)
class Version:
version_id: str = field(
metadata={
"name": "VersionId",
"type": "Attribute",
"required": True,
}
)
+524
View File
@@ -0,0 +1,524 @@
from dataclasses import dataclass, field
from enum import Enum
from typing import List, Optional
class BitmapFormat(Enum):
PNG = "png"
JPG = "jpg"
@dataclass(slots=True, kw_only=True)
class Component:
originating_system: Optional[str] = field(
default=None,
metadata={
"name": "OriginatingSystem",
"type": "Element",
"min_length": 1,
"white_space": "collapse",
}
)
authoring_tool_id: Optional[str] = field(
default=None,
metadata={
"name": "AuthoringToolId",
"type": "Element",
"min_length": 1,
"white_space": "collapse",
}
)
ifc_guid: Optional[str] = field(
default=None,
metadata={
"name": "IfcGuid",
"type": "Attribute",
"length": 22,
"pattern": r"[0-9A-Za-z_$]*",
}
)
@dataclass(slots=True, kw_only=True)
class Direction:
x: float = field(
metadata={
"name": "X",
"type": "Element",
"required": True,
}
)
y: float = field(
metadata={
"name": "Y",
"type": "Element",
"required": True,
}
)
z: float = field(
metadata={
"name": "Z",
"type": "Element",
"required": True,
}
)
@dataclass(slots=True, kw_only=True)
class Point:
x: float = field(
metadata={
"name": "X",
"type": "Element",
"required": True,
}
)
y: float = field(
metadata={
"name": "Y",
"type": "Element",
"required": True,
}
)
z: float = field(
metadata={
"name": "Z",
"type": "Element",
"required": True,
}
)
@dataclass(slots=True, kw_only=True)
class ViewSetupHints:
spaces_visible: bool = field(
default=False,
metadata={
"name": "SpacesVisible",
"type": "Attribute",
}
)
space_boundaries_visible: bool = field(
default=False,
metadata={
"name": "SpaceBoundariesVisible",
"type": "Attribute",
}
)
openings_visible: bool = field(
default=False,
metadata={
"name": "OpeningsVisible",
"type": "Attribute",
}
)
@dataclass(slots=True, kw_only=True)
class Bitmap:
format: BitmapFormat = field(
metadata={
"name": "Format",
"type": "Element",
"required": True,
}
)
reference: str = field(
metadata={
"name": "Reference",
"type": "Element",
"required": True,
"min_length": 1,
"white_space": "collapse",
}
)
location: Point = field(
metadata={
"name": "Location",
"type": "Element",
"required": True,
}
)
normal: Direction = field(
metadata={
"name": "Normal",
"type": "Element",
"required": True,
}
)
up: Direction = field(
metadata={
"name": "Up",
"type": "Element",
"required": True,
}
)
height: float = field(
metadata={
"name": "Height",
"type": "Element",
"required": True,
}
)
@dataclass(slots=True, kw_only=True)
class ClippingPlane:
location: Point = field(
metadata={
"name": "Location",
"type": "Element",
"required": True,
}
)
direction: Direction = field(
metadata={
"name": "Direction",
"type": "Element",
"required": True,
}
)
@dataclass(slots=True, kw_only=True)
class ComponentColoringColorComponents:
class Meta:
global_type = False
component: List[Component] = field(
default_factory=list,
metadata={
"name": "Component",
"type": "Element",
"min_occurs": 1,
}
)
@dataclass(slots=True, kw_only=True)
class ComponentSelection:
component: List[Component] = field(
default_factory=list,
metadata={
"name": "Component",
"type": "Element",
}
)
@dataclass(slots=True, kw_only=True)
class ComponentVisibilityExceptions:
class Meta:
global_type = False
component: List[Component] = field(
default_factory=list,
metadata={
"name": "Component",
"type": "Element",
}
)
@dataclass(slots=True, kw_only=True)
class Line:
start_point: Point = field(
metadata={
"name": "StartPoint",
"type": "Element",
"required": True,
}
)
end_point: Point = field(
metadata={
"name": "EndPoint",
"type": "Element",
"required": True,
}
)
@dataclass(slots=True, kw_only=True)
class OrthogonalCamera:
"""
Attributes
camera_view_point:
camera_direction:
camera_up_vector:
view_to_world_scale: view's visible vertical size in meters
aspect_ratio: Proportional relationship between the width and
the height of the view (w/h).
"""
camera_view_point: Point = field(
metadata={
"name": "CameraViewPoint",
"type": "Element",
"required": True,
}
)
camera_direction: Direction = field(
metadata={
"name": "CameraDirection",
"type": "Element",
"required": True,
}
)
camera_up_vector: Direction = field(
metadata={
"name": "CameraUpVector",
"type": "Element",
"required": True,
}
)
view_to_world_scale: float = field(
metadata={
"name": "ViewToWorldScale",
"type": "Element",
"required": True,
}
)
aspect_ratio: float = field(
metadata={
"name": "AspectRatio",
"type": "Element",
"required": True,
"min_exclusive": 0.0,
}
)
@dataclass(slots=True, kw_only=True)
class PerspectiveCamera:
"""
Attributes
camera_view_point:
camera_direction:
camera_up_vector:
field_of_view: Vertical field of view, in degrees. It is
currently limited to a value between 45 and 60 degrees. This
limitation will be dropped in the next release and viewers
should be expect values outside this range in current
implementations.
aspect_ratio: Proportional relationship between the width and
the height of the view (w/h).
"""
camera_view_point: Point = field(
metadata={
"name": "CameraViewPoint",
"type": "Element",
"required": True,
}
)
camera_direction: Direction = field(
metadata={
"name": "CameraDirection",
"type": "Element",
"required": True,
}
)
camera_up_vector: Direction = field(
metadata={
"name": "CameraUpVector",
"type": "Element",
"required": True,
}
)
field_of_view: float = field(
metadata={
"name": "FieldOfView",
"type": "Element",
"required": True,
"min_exclusive": 0.0,
"max_exclusive": 180.0,
}
)
aspect_ratio: float = field(
metadata={
"name": "AspectRatio",
"type": "Element",
"required": True,
"min_exclusive": 0.0,
}
)
@dataclass(slots=True, kw_only=True)
class ComponentColoringColor:
class Meta:
global_type = False
components: ComponentColoringColorComponents = field(
metadata={
"name": "Components",
"type": "Element",
"required": True,
}
)
color: str = field(
metadata={
"name": "Color",
"type": "Attribute",
"required": True,
"pattern": r"[0-9A-Fa-f]{6}([0-9A-Fa-f]{2})?",
}
)
@dataclass(slots=True, kw_only=True)
class ComponentVisibility:
view_setup_hints: Optional[ViewSetupHints] = field(
default=None,
metadata={
"name": "ViewSetupHints",
"type": "Element",
}
)
exceptions: Optional[ComponentVisibilityExceptions] = field(
default=None,
metadata={
"name": "Exceptions",
"type": "Element",
}
)
default_visibility: bool = field(
default=False,
metadata={
"name": "DefaultVisibility",
"type": "Attribute",
}
)
@dataclass(slots=True, kw_only=True)
class VisualizationInfoBitmaps:
class Meta:
global_type = False
bitmap: List[Bitmap] = field(
default_factory=list,
metadata={
"name": "Bitmap",
"type": "Element",
}
)
@dataclass(slots=True, kw_only=True)
class VisualizationInfoClippingPlanes:
class Meta:
global_type = False
clipping_plane: List[ClippingPlane] = field(
default_factory=list,
metadata={
"name": "ClippingPlane",
"type": "Element",
}
)
@dataclass(slots=True, kw_only=True)
class VisualizationInfoLines:
class Meta:
global_type = False
line: List[Line] = field(
default_factory=list,
metadata={
"name": "Line",
"type": "Element",
}
)
@dataclass(slots=True, kw_only=True)
class ComponentColoring:
color: List[ComponentColoringColor] = field(
default_factory=list,
metadata={
"name": "Color",
"type": "Element",
}
)
@dataclass(slots=True, kw_only=True)
class Components:
selection: Optional[ComponentSelection] = field(
default=None,
metadata={
"name": "Selection",
"type": "Element",
}
)
visibility: Optional[ComponentVisibility] = field(
default=None,
metadata={
"name": "Visibility",
"type": "Element",
}
)
coloring: Optional[ComponentColoring] = field(
default=None,
metadata={
"name": "Coloring",
"type": "Element",
}
)
@dataclass(slots=True, kw_only=True)
class VisualizationInfo:
"""
VisualizationInfo documentation.
"""
components: Optional[Components] = field(
default=None,
metadata={
"name": "Components",
"type": "Element",
}
)
orthogonal_camera: Optional[OrthogonalCamera] = field(
default=None,
metadata={
"name": "OrthogonalCamera",
"type": "Element",
}
)
perspective_camera: Optional[PerspectiveCamera] = field(
default=None,
metadata={
"name": "PerspectiveCamera",
"type": "Element",
}
)
lines: Optional[VisualizationInfoLines] = field(
default=None,
metadata={
"name": "Lines",
"type": "Element",
}
)
clipping_planes: Optional[VisualizationInfoClippingPlanes] = field(
default=None,
metadata={
"name": "ClippingPlanes",
"type": "Element",
}
)
bitmaps: Optional[VisualizationInfoBitmaps] = field(
default=None,
metadata={
"name": "Bitmaps",
"type": "Element",
}
)
guid: str = field(
metadata={
"name": "Guid",
"type": "Attribute",
"required": True,
"pattern": r"[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}",
}
)
+188
View File
@@ -0,0 +1,188 @@
"""BCF XML V3 Topic handler."""
import datetime
import uuid
import zipfile
from pathlib import Path
from typing import Any, Optional
from ifcopenshell import entity_instance
from xsdata.models.datatype import XmlDateTime
import bcf.v3.model as mdl
from bcf.inmemory_zipfile import ZipFileInterface
from bcf.v3.visinfo import VisualizationInfoHandler
from bcf.xml_parser import AbstractXmlParserSerializer, XmlParserSerializer
class TopicHandler:
"""BCF Topic and related objects handler."""
def __init__(
self,
topic_dir: Optional[zipfile.Path] = None,
xml_handler: Optional[AbstractXmlParserSerializer] = None,
) -> None:
self._markup: Optional[mdl.Markup] = None
self._viewpoints: dict[str, VisualizationInfoHandler] = {}
self._bim_snippet: Optional[bytes] = None
self._xml_handler = xml_handler or XmlParserSerializer()
self._topic_dir = topic_dir
@property
def markup(self) -> Optional[mdl.Markup]:
if not self._markup and self._topic_dir:
markup_path = self._topic_dir.joinpath("markup.bcf")
if markup_path.exists():
self._markup = self._xml_handler.parse(markup_path.read_bytes(), mdl.Markup)
return self._markup
@markup.setter
def markup(self, value: mdl.Markup) -> None:
self._markup = value
@property
def topic(self) -> mdl.Topic:
"""Return the Topic object."""
return self.markup.topic
@property
def guid(self) -> Optional[str]:
"""Return the GUID of the topic."""
if self._markup:
return self.topic.guid
return self._topic_dir.name if self._topic_dir else None
@property
def header(self) -> Optional[mdl.Header]:
"""Return the header of the topic."""
return self.markup.header
@property
def comments(self) -> list[mdl.Comment]:
"""Return the comments of the topic."""
return self.topic.comments.comment if self.topic.comments else []
@property
def bim_snippet(self) -> Optional[bytes]:
if not self._bim_snippet and self._topic_dir:
self._bim_snippet = self._load_bim_snippet()
return self._bim_snippet
@bim_snippet.setter
def bim_snippet(self, value: bytes) -> None:
self._bim_snippet = value
@property
def viewpoints(self) -> Optional[VisualizationInfoHandler]:
if (
not self._viewpoints
and self._topic_dir
and self.topic.viewpoints
and (viewpoints := self.topic.viewpoints.view_point)
):
self._viewpoints = VisualizationInfoHandler.from_topic_viewpoints(self._topic_dir, viewpoints)
return self._viewpoints
def _load_bim_snippet(self) -> Optional[bytes]:
bim_snippet_obj = self.topic.bim_snippet
if bim_snippet_obj and not bim_snippet_obj.is_external and self._topic_dir:
bim_snippet_path = self._topic_dir.joinpath(bim_snippet_obj.reference)
if bim_snippet_path.exists():
return bim_snippet_path.read_bytes()
return None
@classmethod
def create_new(
cls,
title: str,
description: str,
author: str,
topic_type: str = "",
topic_status: str = "",
xml_handler: Optional[AbstractXmlParserSerializer] = None,
) -> "TopicHandler":
"""
Create a new BCF topic.
Args:
title: The title of the topic.
description: The description of the topic.
author: The author of the topic.
topic_type: The type of the topic.
topic_status: The status of the topic.
xml_handler: The XML parser/serializer to use.
Returns:
The BCF topic definition.
"""
creation_date = XmlDateTime.from_datetime(datetime.datetime.now())
guid = str(uuid.uuid4())
topic = mdl.Topic(
title=title,
description=description,
creation_author=author,
creation_date=creation_date,
guid=guid,
topic_type=topic_type,
topic_status=topic_status,
)
markup = mdl.Markup(topic=topic)
obj = cls(topic_dir=Path(guid), xml_handler=xml_handler or XmlParserSerializer())
obj.markup = markup
return obj
def save(self, destination_zip: ZipFileInterface) -> None:
"""
Save the topic to a BCF zip file.
Args:
bcf_zip: The BCF zip file to save to.
"""
topic_dir = self.guid
self._save_xml(destination_zip, self._markup, "markup.bcf")
self._save_viewpoints(destination_zip, topic_dir)
self._save_bim_snippet(destination_zip)
def _save_viewpoints(self, destination_zip, topic_dir) -> None:
if not self.topic.viewpoints or not (viewpoints := self.topic.viewpoints.view_point):
return
for vpt in viewpoints:
if vpt.viewpoint:
self.viewpoints[vpt.viewpoint].save(destination_zip, topic_dir, vpt)
def _save_xml(self, destination_zip: ZipFileInterface, item: Any, target: str) -> None:
to_write = self._xml_handler.serialize(item) if item else self._topic_dir.joinpath(target).read_bytes()
destination_zip.writestr(f"{self._topic_dir.name}/{target}", to_write)
def _save_bim_snippet(self, destination_zip: ZipFileInterface) -> None:
snippet = self.topic.bim_snippet
if not snippet or snippet.is_external:
return
ref_filename = Path(snippet.reference).name
if self.bim_snippet:
destination_zip.writestr(f"{self.topic.guid}/{ref_filename}", self.bim_snippet)
def add_viewpoint(self, element: entity_instance) -> None:
"""
Add a viewpoint tergeting an IFC element to the topic.
Args:
element: The IFC element.
"""
new_viewpoint = VisualizationInfoHandler.create_new(element, self._xml_handler)
self.add_visinfo_handler(new_viewpoint)
def add_visinfo_handler(self, new_viewpoint: VisualizationInfoHandler) -> None:
self.viewpoints[new_viewpoint.guid] = new_viewpoint
if self.topic.viewpoints is None:
self.topic.viewpoints = mdl.TopicViewpoints()
self.topic.viewpoints.view_point.append(mdl.ViewPoint(viewpoint=new_viewpoint.guid, guid=new_viewpoint.guid))
def __eq__(self, other: object) -> bool:
if not isinstance(other, TopicHandler):
raise TypeError("Equality needs a BcfXml object.")
return (
self.markup == other.markup
and self.viewpoints == other.viewpoints
and self.bim_snippet == other.bim_snippet
)
+247
View File
@@ -0,0 +1,247 @@
import uuid
import zipfile
from functools import lru_cache
from typing import Any, Iterable, Optional
import numpy as np
from ifcopenshell import entity_instance
from ifcopenshell.util import placement
from numpy.typing import NDArray
import bcf.v3.model as mdl
from bcf.geometry import calc_camera_vectors
from bcf.inmemory_zipfile import ZipFileInterface
from bcf.xml_parser import AbstractXmlParserSerializer, XmlParserSerializer
class VisualizationInfoHandler:
"""Handle the VisualizationInfo and related objects."""
def __init__(
self,
visualization_info: mdl.VisualizationInfo,
snapshot: Optional[bytes] = None,
bitmaps: Optional[dict[str, bytes]] = None,
xml_handler: Optional[AbstractXmlParserSerializer] = None,
) -> None:
self.visualization_info = visualization_info
self.snapshot = snapshot
self.bitmaps = bitmaps or {}
self._xml_handler = xml_handler or XmlParserSerializer()
@property
def guid(self) -> str:
"""Return the GUID of the visualization info."""
return self.visualization_info.guid
@classmethod
def from_topic_viewpoints(
cls,
topic_dir: zipfile.Path,
vps: Iterable[mdl.ViewPoint],
xml_handler: Optional[AbstractXmlParserSerializer] = None,
) -> dict[str, "VisualizationInfoHandler"]:
"""Create VisualizationInfoHandler objects of a Topic's ViewPoints."""
viewpoints = {}
for vpt in vps:
visinfo = cls.load(topic_dir, vpt, xml_handler)
if visinfo and vpt.viewpoint:
viewpoints[vpt.viewpoint] = visinfo
return viewpoints
@classmethod
def load(
cls,
topic_dir: zipfile.Path,
vpt: mdl.ViewPoint,
xml_handler: Optional[AbstractXmlParserSerializer] = None,
) -> Optional["VisualizationInfoHandler"]:
"""
Load the VisualizationInfo and related objects from a BCF zip file.
Args:
topic_dir: The directory in the BCF zip file to load from.
vpt: The ViewPoint to load.
xml_handler: The XML handler to use to parse the VisualizationInfo.
Returns:
The VisualizationInfoHandler object.
"""
visinfo = cls._load_visinfo(topic_dir, vpt.viewpoint, xml_handler)
if not visinfo:
return None
snapshot = cls._load_snapshot(topic_dir, vpt.snapshot)
bitmaps = cls._load_bitmaps(topic_dir, visinfo)
return cls(visinfo, snapshot, bitmaps, xml_handler)
@staticmethod
def _load_visinfo(
topic_dir: zipfile.Path,
vp_name: Optional[str],
xml_handler: Optional[AbstractXmlParserSerializer] = None,
) -> Optional[mdl.VisualizationInfo]:
if not vp_name:
return None
vp_path = topic_dir.joinpath(vp_name)
if vp_path.exists():
xml_handler = xml_handler or XmlParserSerializer()
return xml_handler.parse(vp_path.read_bytes(), mdl.VisualizationInfo)
return None
@staticmethod
def _load_snapshot(topic_dir: zipfile.Path, vp_snapshot: Optional[str]) -> Optional[bytes]:
if vp_snapshot:
snapshot_path = topic_dir.joinpath(vp_snapshot)
if snapshot_path.exists():
return snapshot_path.read_bytes()
return None
@staticmethod
def _load_bitmaps(topic_dir: zipfile.Path, visinfo: Optional[mdl.VisualizationInfo]) -> dict[str, bytes]:
if not visinfo or not (bitmaps := visinfo.bitmaps):
return {}
bitmaps_dict = {}
for bitmap in bitmaps.bitmap:
if not bitmap.reference:
continue
bitmap_path = topic_dir.joinpath(bitmap.reference)
if bitmap_path.exists():
bitmaps_dict[bitmap.reference] = bitmap_path.read_bytes()
return bitmaps_dict
def save(
self,
bcf_zip: ZipFileInterface,
topic_dir: str,
vpt: mdl.ViewPoint,
) -> None:
"""
Save the VisualizationInfo and related objects to a BCF zip file.
Args:
bcf_zip: The BCF zip file to save to.
topic_dir: The directory in the BCF zip file to save to.
vpt: The ViewPoint to save.
"""
if not (vp_name := vpt.viewpoint):
return
self._save_visinfo(bcf_zip, topic_dir, vp_name)
self._save_snapshot(bcf_zip, topic_dir, vpt.snapshot)
self._save_bitmaps(bcf_zip, topic_dir)
def _save_snapshot(self, bcf_zip: ZipFileInterface, topic_dir: str, filename: Optional[str]) -> None:
if self.snapshot and filename:
bcf_zip.writestr(f"{topic_dir}/{filename}", self.snapshot)
def _save_visinfo(self, bcf_zip: ZipFileInterface, topic_dir: str, vp_name: str) -> None:
bcf_zip.writestr(
f"{topic_dir}/{vp_name}",
self._xml_handler.serialize(self.visualization_info),
)
def _save_bitmaps(self, bcf_zip: ZipFileInterface, topic_dir: str) -> None:
if not self.bitmaps:
return
if not (bitmaps_defs := self.visualization_info.bitmaps):
return
for bitmap_def in bitmaps_defs.bitmap:
if not (bitmap_name := bitmap_def.reference):
continue
if bitmap_name in self.bitmaps:
bcf_zip.writestr(f"{topic_dir}/{bitmap_name}", self.bitmaps[bitmap_name])
@classmethod
def create_new(
cls,
element: entity_instance,
xml_handler: Optional[AbstractXmlParserSerializer] = None,
) -> "VisualizationInfoHandler":
"""
Create a new VisualizationInfoHandler object from an IFC element.
Args:
element: The IFC element to point at.
xml_handler: The XML handler to use.
Returns:
The VisualizationInfoHandler object.
"""
xml_handler = xml_handler or XmlParserSerializer()
return cls(visualization_info=build_viewpoint(element), xml_handler=xml_handler)
@lru_cache(maxsize=None)
def build_viewpoint(element: entity_instance) -> mdl.VisualizationInfo:
"""
Return a BCF viewpoint of an IFC element.
This function is cached to speedudp the creation of multiple BCF topics regarding the same element.
Args:
element: The IFC element to point at.
Returns:
The BCF viewpoint definition.
"""
elem_placement = placement.get_local_placement(element.ObjectPlacement)
return mdl.VisualizationInfo(
guid=str(uuid.uuid4()),
components=build_components(element.GlobalId),
perspective_camera=build_camera(elem_placement),
)
def build_components(guid: str) -> mdl.Components:
"""
Return the BCF components from an IFC element GUID.
Args:
guid: The IFC element GUID.
Returns:
The BCF components definition.
"""
return mdl.Components(
selection=mdl.ComponentSelection(component=[mdl.Component(ifc_guid=guid)]),
visibility=mdl.ComponentVisibility(default_visibility=True),
)
def build_camera(elem_placement: NDArray[np.float_]) -> mdl.PerspectiveCamera:
"""
Return a BCF camera for an IFC element placement matrix.
Args:
elem_placement: The IFC element placement as a rototranslation matrix.
Returns:
The BCF camera definition.
"""
return build_camera_from_vectors(*calc_camera_vectors(elem_placement))
def build_camera_from_vectors(
camera_position: NDArray[np.float_], camera_dir: NDArray[np.float_], camera_up: NDArray[np.float_]
) -> mdl.PerspectiveCamera:
"""
Return a BCF camera for an IFC element placement matrix.
Args:
camera_position: camera position array
camera_dir: camera direction versor
camera_up_vector: camera up versor
Returns:
The BCF camera definition.
"""
camera_viewpoint = mdl.Point(x=camera_position[0], y=camera_position[1], z=camera_position[2])
camera_direction = mdl.Direction(x=camera_dir[0], y=camera_dir[1], z=camera_dir[2])
camera_up_vector = mdl.Direction(x=camera_up[0], y=camera_up[1], z=camera_up[2])
return mdl.PerspectiveCamera(
camera_view_point=camera_viewpoint,
camera_direction=camera_direction,
camera_up_vector=camera_up_vector,
aspect_ratio=1.0,
field_of_view=60.0,
)
+83
View File
@@ -0,0 +1,83 @@
"""XML Parser and Serializer factories."""
from typing import Optional, Protocol, Type, TypeVar
from xsdata.formats.dataclass.context import XmlContext
from xsdata.formats.dataclass.parsers import XmlParser
from xsdata.formats.dataclass.serializers import XmlSerializer
from xsdata.formats.dataclass.serializers.config import SerializerConfig
def build_xml_parser(context: Optional[XmlContext] = None) -> XmlParser:
"""Return a parser for an XML file."""
parser = XmlParser(context=context or XmlContext())
parser.register_namespace("xs", "http://www.w3.org/2001/XMLSchema")
return parser
def build_serializer(context: Optional[XmlContext] = None) -> XmlSerializer:
"""Return a serializer for an XML file."""
return XmlSerializer(
config=SerializerConfig(pretty_print=True),
context=context or XmlContext(),
)
T = TypeVar("T")
class AbstractXmlParserSerializer(Protocol):
"""XML Parser and serializer wrapper."""
def parse(self, xml: bytes, clazz: Type[T]) -> T:
"""
Parse an XML file to an object.
Args:
xml: The XML file as bytes.
clazz: The class to parse to.
"""
def serialize(self, obj: T, ns_map: Optional[dict[str, str]] = None) -> str:
"""
Serialize an object to XML.
Args:
obj: The object to serialize.
ns_map: The namespace map to use.
Returns:
The XML as string.
"""
class XmlParserSerializer:
"""XML Parser and serializer wrapper."""
def __init__(self) -> None:
self.context = XmlContext()
self.parser = build_xml_parser(self.context)
self.serializer = build_serializer(self.context)
def parse(self, xml: bytes, clazz: Type[T]) -> T:
"""
Parse an XML file to an object.
Args:
xml: The XML file as bytes.
clazz: The class to parse to.
"""
return self.parser.from_bytes(xml, clazz)
def serialize(self, obj: T, ns_map: Optional[dict[str, str]] = None) -> str:
"""
Serialize an object to XML.
Args:
obj: The object to serialize.
ns_map: The namespace map to use.
Returns:
The XML as string.
"""
ns_map = ns_map or {"xs": "http://www.w3.org/2001/XMLSchema"}
return self.serializer.render(obj, ns_map)
+1
View File
@@ -0,0 +1 @@
"""BCF tests."""
+8
View File
@@ -0,0 +1,8 @@
import pytest
from bcf.xml_parser import XmlParserSerializer
@pytest.fixture(scope="session")
def xml_handler() -> XmlParserSerializer:
return XmlParserSerializer()
Binary file not shown.
View File
+122
View File
@@ -0,0 +1,122 @@
"""BCF XML tests."""
import uuid
from pathlib import Path
from tempfile import TemporaryDirectory
import pytest
import bcf.v2.model as mdl
from bcf.v2.bcfxml import BcfXml
from bcf.v2.topic import TopicHandler
from bcf.v2.visinfo import (
VisualizationInfoHandler,
build_camera_from_vectors,
build_components,
)
from bcf.xml_parser import XmlParserSerializer
@pytest.fixture()
def build_sample(xml_handler: XmlParserSerializer) -> tuple[BcfXml, TopicHandler]:
bcf = BcfXml.create_new("Test project", xml_handler=xml_handler)
orig_th = bcf.add_topic("Test topic", "Test message", "Test author", "Test type")
return bcf, orig_th
def test_bcf_roundtrip(xml_handler, build_sample) -> None:
"""Saving and loading a bcf xml project returns the same objects."""
bcf, orig_th = build_sample
with TemporaryDirectory() as tmp_dir:
file_path = Path(tmp_dir) / "test.bcf"
bcf.save(file_path)
with BcfXml.load(file_path, xml_handler=xml_handler) as parsed:
assert parsed == bcf
parsed_th = parsed.topics[orig_th.guid]
assert parsed_th == orig_th
def test_bcf_edit_saveas(xml_handler, build_sample) -> None:
"""Saving and loading a bcf xml project returns the same objects."""
bcf, orig_th = build_sample
with TemporaryDirectory() as tmp_dir:
file_path = Path(tmp_dir) / "test.bcf"
bcf.save(file_path)
with BcfXml.load(file_path, xml_handler=xml_handler) as parsed:
for th in parsed.topics.values():
th.topic.title = "New Topic Title"
modified_path = Path(tmp_dir) / "edited.bcf"
parsed.save(modified_path)
with BcfXml.load(modified_path, xml_handler=xml_handler) as modified_parsed:
assert modified_parsed == bcf
parsed_th = modified_parsed.topics[orig_th.guid]
assert parsed_th.markup != orig_th.markup
assert parsed_th.markup == parsed.topics[orig_th.guid].markup
assert parsed_th.viewpoints == orig_th.viewpoints
assert parsed_th.bim_snippet == orig_th.bim_snippet
def test_bcf_edit(xml_handler, build_sample) -> None:
"""Saving and loading a bcf xml project returns the same objects."""
bcf, orig_th = build_sample
with TemporaryDirectory() as tmp_dir:
file_path = Path(tmp_dir) / "test.bcf"
bcf.save(file_path)
with BcfXml.load(file_path, xml_handler=xml_handler) as parsed:
for th in parsed.topics.values():
th.topic.title = "New Topic Title"
parsed.save()
with BcfXml.load(file_path, xml_handler=xml_handler) as modified_parsed:
assert modified_parsed == bcf
assert len(modified_parsed.topics) == 1
parsed_th = modified_parsed.topics[orig_th.guid]
assert parsed_th.markup != orig_th.markup
assert parsed_th.markup == parsed.topics[orig_th.guid].markup
assert parsed_th.viewpoints == orig_th.viewpoints
assert parsed_th.bim_snippet == orig_th.bim_snippet
def test_save_no_filename(build_sample) -> None:
bcf, _ = build_sample
with pytest.raises(ValueError):
bcf.save()
def test_load_no_filename() -> None:
with pytest.raises(ValueError):
BcfXml.load("")
def test_save_keep_open(build_sample) -> None:
bcf, _ = build_sample
with TemporaryDirectory() as tmp_dir:
file_path = Path(tmp_dir) / "test.bcf"
bcf.save(file_path, keep_open=True)
assert bcf._zip_file is not None
bcf._zip_file.close()
def test_massive_bcf(xml_handler) -> None:
bcf = BcfXml.create_new("Test project", xml_handler=xml_handler)
for i in range(100):
th = bcf.add_topic(f"Topic {i:04}", f"Message {i:04}", "Test author", "Test type")
vi = mdl.VisualizationInfo(
guid=str(uuid.uuid4()),
components=build_components(str(uuid.uuid4())),
perspective_camera=build_camera_from_vectors([i, 0, 0], [0, 1, 0], [0, 0, 1]),
)
vh = VisualizationInfoHandler(visualization_info=vi, xml_handler=xml_handler)
th.add_visinfo_handler(vh)
assert len(bcf.topics) == 100
with TemporaryDirectory() as tmp_dir:
file_path = Path(tmp_dir) / "test.bcf"
bcf.save(file_path)
def test_equality_with_wrong_object() -> None:
with pytest.raises(TypeError):
build_sample[0] == "Wrong object"
def test_topic_equality_with_wrong_object() -> None:
with pytest.raises(TypeError):
build_sample[1] == "Wrong object"
+365
View File
@@ -0,0 +1,365 @@
import json
import os
import zipfile
from pathlib import Path
from xsdata.models.datatype import XmlDateTime
import bcf.v2.model as mdl
from bcf.v2.bcfxml import BcfXml
from bcf.v2.topic import TopicHandler
def test_maximum_information() -> None:
"""
All the info in a BCF is parsed correctly.
Uses sample file from https://github.com/buildingSMART/BCF-XML/
"""
bcf_path = Path(__file__).parent / "MaximumInformation.bcf"
with BcfXml.load(bcf_path) as bcf:
assert_everything_in_place(bcf)
def test_save_maximum_information() -> None:
base_dir = Path(__file__).parent
bcf_path = base_dir / "MaximumInformation.bcf"
target_path = base_dir / "MaximumInformationSaved.bcf"
with BcfXml.load(bcf_path) as bcf:
bcf.save(target_path)
assert target_path.exists()
with BcfXml.load(target_path) as bcf2:
assert_everything_in_place(bcf2)
expected_files = [
"bcf.version",
"project.bcfp",
"7ddc3ef0-0ab7-43f1-918a-45e38b42369c/markup.bcf",
"7ddc3ef0-0ab7-43f1-918a-45e38b42369c/bitmap.png",
"7ddc3ef0-0ab7-43f1-918a-45e38b42369c/tux.png",
"7ddc3ef0-0ab7-43f1-918a-45e38b42369c/JsonElement.json",
"7ddc3ef0-0ab7-43f1-918a-45e38b42369c/Viewpoint_4ab7514b-b216-4d56-98d2-45cf8500ff5a.bcfv",
"7ddc3ef0-0ab7-43f1-918a-45e38b42369c/Viewpoint_9a4a1878-ecbd-4916-83a8-dad82e560231.bcfv",
"7ddc3ef0-0ab7-43f1-918a-45e38b42369c/Viewpoint_fc4019d7-365e-47f3-b6d0-b39fc48f15fc.bcfv",
"7ddc3ef0-0ab7-43f1-918a-45e38b42369c/Snapshot_4ab7514b-b216-4d56-98d2-45cf8500ff5a.png",
"7ddc3ef0-0ab7-43f1-918a-45e38b42369c/Snapshot_9a4a1878-ecbd-4916-83a8-dad82e560231.png",
"7ddc3ef0-0ab7-43f1-918a-45e38b42369c/Snapshot_fc4019d7-365e-47f3-b6d0-b39fc48f15fc.png",
"d1068c81-af04-4546-b63c-348810f6c716/markup.bcf",
"extensions.xsd",
"IfcPile_01.ifc",
"markup.xsd",
]
assert_files_present(target_path, expected_files)
os.unlink(target_path)
def assert_everything_in_place(bcf: BcfXml):
assert bcf.version.version_id == "2.1"
assert bcf.project.name == "BCF API Implementation"
assert bcf.project_info.extension_schema == "extensions.xsd"
assert len(bcf.topics) == 2
assert_first_topic_handler(bcf.topics["7ddc3ef0-0ab7-43f1-918a-45e38b42369c"])
second_th = bcf.topics["d1068c81-af04-4546-b63c-348810f6c716"]
assert second_th.topic == mdl.Topic(
title="Referenced topic",
creation_date=XmlDateTime(2017, 5, 22, 7, 51, 0, 42987900),
creation_author="dangl@iabi.eu",
description="This is just an empty topic that acts as a referenced topic.",
guid="d1068c81-af04-4546-b63c-348810f6c716",
)
def assert_first_topic_handler(topic_handler: TopicHandler):
assert topic_handler.guid == "7ddc3ef0-0ab7-43f1-918a-45e38b42369c"
expected_bs1 = mdl.BimSnippet(
reference="JsonElement.json", reference_schema="http://json-schema.org", snippet_type="JSON"
)
expected_t1 = mdl.Topic(
reference_link=["https://bim--it.net"],
title="Maximum Content",
priority="High",
index=0,
labels=["Structural", "IT Development"],
creation_date=XmlDateTime(2015, 6, 21, 12, 0, 0),
creation_author="dangl@iabi.eu",
modified_date=XmlDateTime(2015, 6, 21, 14, 22, 47),
modified_author="dangl@iabi.eu",
due_date=XmlDateTime(2016, 10, 2, 14, 22, 47),
assigned_to="linhard@iabi.eu",
stage="Construction Start",
description="This is a topic with all informations present.",
bim_snippet=expected_bs1,
document_reference=[
mdl.TopicDocumentReference(
referenced_document="https://github.com/BuildingSMART/BCF-XML",
description="GitHub BCF Specification",
is_external=True,
),
mdl.TopicDocumentReference(
referenced_document="../markup.xsd",
description="Markup.xsd Schema",
is_external=False,
),
],
related_topic=[mdl.TopicRelatedTopic(guid="d1068c81-af04-4546-b63c-348810f6c716")],
guid="7ddc3ef0-0ab7-43f1-918a-45e38b42369c",
topic_type="Structural",
topic_status="Open",
)
assert topic_handler.topic == expected_t1
expected_h1 = mdl.Header(
file=[
mdl.HeaderFile(
filename="IfcPile_01.ifc",
date=XmlDateTime(2014, 10, 27, 16, 27, 27),
reference="../IfcPile_01.ifc",
ifc_project="0M6o7Znnv7hxsbWgeu7oQq",
ifc_spatial_structure_element="23B$bNeGHFQuMYJzvUX0FD",
is_external=False,
)
]
)
assert topic_handler.header == expected_h1
expected_th1_comments = [
mdl.Comment(
date=XmlDateTime(2015, 8, 31, 12, 40, 17),
author="dangl@iabi.eu",
comment="This is an unmodified topic at the uppermost hierarchical level.\nAll times in the XML are marked as UTC times.",
guid="07ccdba0-1736-47e1-807d-67dc6f3addaa",
),
mdl.Comment(
date=XmlDateTime(2015, 8, 31, 14, 0, 1),
author="dangl@iabi.eu",
comment="This comment was a reply to the first comment in BCF v2.0. This is a no longer supported functionality and therefore is to be treated as a regular comment in v2.1.",
guid="a12766c2-61bc-40b4-ab19-e9f45fd0b0bf",
),
mdl.Comment(
date=XmlDateTime(2015, 8, 31, 13, 7, 11),
author="dangl@iabi.eu",
comment="This comment again is in the highest hierarchy level.\nIt references a viewpoint.",
viewpoint=mdl.CommentViewpoint(guid="4ab7514b-b216-4d56-98d2-45cf8500ff5a"),
guid="c2bb5bb0-773d-45dd-bdaa-19a216439ed3",
),
mdl.Comment(
date=XmlDateTime(2015, 8, 31, 15, 42, 58),
author="dangl@iabi.eu",
comment="This comment contained some spllng errs.\nHopefully, the modifier did catch them all.",
modified_date=XmlDateTime(2015, 8, 31, 16, 7, 11),
modified_author="dangl@iabi.eu",
guid="0b843a5c-c3bf-41ef-be98-52a9f7bd9790",
),
]
assert topic_handler.comments == expected_th1_comments
expected_th1_viewpoints = [
mdl.ViewPoint(
viewpoint="Viewpoint_4ab7514b-b216-4d56-98d2-45cf8500ff5a.bcfv",
snapshot="Snapshot_4ab7514b-b216-4d56-98d2-45cf8500ff5a.png",
index=2,
guid="4ab7514b-b216-4d56-98d2-45cf8500ff5a",
),
mdl.ViewPoint(
viewpoint="Viewpoint_fc4019d7-365e-47f3-b6d0-b39fc48f15fc.bcfv",
snapshot="Snapshot_fc4019d7-365e-47f3-b6d0-b39fc48f15fc.png",
index=0,
guid="fc4019d7-365e-47f3-b6d0-b39fc48f15fc",
),
mdl.ViewPoint(
viewpoint="Viewpoint_9a4a1878-ecbd-4916-83a8-dad82e560231.bcfv",
snapshot="Snapshot_9a4a1878-ecbd-4916-83a8-dad82e560231.png",
index=1,
guid="9a4a1878-ecbd-4916-83a8-dad82e560231",
),
]
expected_m1 = mdl.Markup(
header=expected_h1,
topic=expected_t1,
comment=expected_th1_comments,
viewpoints=expected_th1_viewpoints,
)
assert topic_handler.markup == expected_m1
assert json.loads(topic_handler.bim_snippet) == {"Material": "Concrete", "Temperatures": ["Cold", "Hot", "Hotter"]}
assert topic_handler.reference_files["../IfcPile_01.ifc"] is not None
assert_viewpoints(topic_handler.viewpoints)
def assert_viewpoints(viewpoints):
assert len(viewpoints) == 3
expected_selection = mdl.ComponentSelection(
component=[
mdl.Component(ifc_guid="0cSRUx$EX1NRjqiKcYQ$a0"),
mdl.Component(ifc_guid="1jQQiGIAnFzxOUzrdmJYDS"),
mdl.Component(ifc_guid="0fdpeZZEX3FwJ7x0ox5kzF"),
mdl.Component(ifc_guid="23Zwlpd71EyvHlH6OZ77nK"),
mdl.Component(ifc_guid="1OpjQ1Nlv4sQuTxfUC_8zS"),
]
)
expected_exception = mdl.ComponentVisibilityExceptions(
component=[
mdl.Component(ifc_guid="0Gl71cVurFn8bxAOox6M4X"),
mdl.Component(ifc_guid="23Zwlpd71EyvHlH6OZ77nK"),
mdl.Component(ifc_guid="3DvyPxGIn8qR0KDwbL_9r1"),
mdl.Component(ifc_guid="0fdpeZZEX3FwJ7x0ox5kzF"),
mdl.Component(ifc_guid="1OpjQ1Nlv4sQuTxfUC_8zS"),
]
)
expected_coloring = mdl.ComponentColoring(
color=[
mdl.ComponentColoringColor(
component=[
mdl.Component(ifc_guid="0fdpeZZEX3FwJ7x0ox5kzF"),
mdl.Component(ifc_guid="23Zwlpd71EyvHlH6OZ77nK"),
mdl.Component(ifc_guid="1OpjQ1Nlv4sQuTxfUC_8zS"),
mdl.Component(ifc_guid="0cSRUx$EX1NRjqiKcYQ$a0"),
],
color="3498DB",
)
]
)
assert_first_viewpoint(
viewpoints["Viewpoint_4ab7514b-b216-4d56-98d2-45cf8500ff5a.bcfv"],
expected_selection,
expected_exception,
expected_coloring,
)
assert_second_viewpoint(
viewpoints["Viewpoint_fc4019d7-365e-47f3-b6d0-b39fc48f15fc.bcfv"],
expected_selection,
expected_exception,
expected_coloring,
)
assert_third_viewpoint(
viewpoints["Viewpoint_9a4a1878-ecbd-4916-83a8-dad82e560231.bcfv"],
expected_selection,
expected_exception,
expected_coloring,
)
def assert_first_viewpoint(viewpoint, expected_selection, expected_exception, expected_coloring) -> None:
expected_vp = mdl.VisualizationInfo(
components=mdl.Components(
view_setup_hints=mdl.ViewSetupHints(
spaces_visible=True,
space_boundaries_visible=True,
openings_visible=True,
),
selection=expected_selection,
visibility=mdl.ComponentVisibility(
exceptions=expected_exception,
default_visibility=True,
),
coloring=expected_coloring,
),
perspective_camera=mdl.PerspectiveCamera(
camera_view_point=mdl.Point(x=0.43079984188079834, y=69.52057647705078, z=10.666350364685059),
camera_direction=mdl.Direction(x=0.09159398823976517, y=-0.9375035166740417, z=-0.3357048034667969),
camera_up_vector=mdl.Direction(x=0.01938679628074169, y=-0.3353792130947113, z=0.9418837428092957),
field_of_view=60,
),
lines=mdl.VisualizationInfoLines(
line=[
mdl.Line(start_point=mdl.Point(x=0, y=0, z=0), end_point=mdl.Point(x=0, y=0, z=1)),
mdl.Line(start_point=mdl.Point(x=0, y=0, z=1), end_point=mdl.Point(x=0, y=1, z=1)),
mdl.Line(start_point=mdl.Point(x=0, y=1, z=1), end_point=mdl.Point(x=1, y=1, z=1)),
]
),
clipping_planes=mdl.VisualizationInfoClippingPlanes(
clipping_plane=[
mdl.ClippingPlane(location=mdl.Point(x=0, y=0, z=0), direction=mdl.Direction(x=0, y=0, z=1)),
mdl.ClippingPlane(location=mdl.Point(x=0, y=0, z=0), direction=mdl.Direction(x=0, y=1, z=0)),
]
),
bitmap=[
mdl.VisualizationInfoBitmap(
bitmap=mdl.BitmapFormat.PNG,
reference="bitmap.png",
location=mdl.Point(x=10, y=-10, z=7),
normal=mdl.Direction(x=0, y=1, z=0),
up=mdl.Direction(x=0, y=0, z=1),
height=5.0,
),
mdl.VisualizationInfoBitmap(
bitmap=mdl.BitmapFormat.PNG,
reference="tux.png",
location=mdl.Point(x=20, y=-10, z=7),
normal=mdl.Direction(x=0, y=1, z=0),
up=mdl.Direction(x=0, y=0, z=1),
height=5.0,
),
],
guid="8dc86298-9737-40b4-a448-98a9e953293a",
)
assert viewpoint.visualization_info == expected_vp
assert viewpoint.snapshot is not None
def assert_second_viewpoint(viewpoint, expected_selection, expected_exception, expected_coloring) -> None:
expected_vp = mdl.VisualizationInfo(
components=mdl.Components(
view_setup_hints=mdl.ViewSetupHints(
spaces_visible=False,
space_boundaries_visible=False,
openings_visible=False,
),
selection=expected_selection,
visibility=mdl.ComponentVisibility(
exceptions=expected_exception,
default_visibility=False,
),
coloring=expected_coloring,
),
perspective_camera=mdl.PerspectiveCamera(
camera_view_point=mdl.Point(x=-47.18794250488281, y=43.829200744628906, z=10.666350364685059),
camera_direction=mdl.Direction(x=0.6745243072509766, y=-0.6599355936050415, z=-0.33091068267822266),
camera_up_vector=mdl.Direction(x=0.2271970510482788, y=-0.24091780185699463, z=0.9435783624649048),
field_of_view=60,
),
guid="21dd4807-e9af-439e-a980-04d913a6b1ce",
)
assert viewpoint.visualization_info == expected_vp
assert viewpoint.snapshot is not None
def assert_third_viewpoint(viewpoint, expected_selection, expected_exception, expected_coloring) -> None:
expected_vp = mdl.VisualizationInfo(
components=mdl.Components(
view_setup_hints=mdl.ViewSetupHints(
spaces_visible=False,
space_boundaries_visible=False,
openings_visible=True,
),
selection=expected_selection,
visibility=mdl.ComponentVisibility(
exceptions=expected_exception,
default_visibility=True,
),
coloring=expected_coloring,
),
perspective_camera=mdl.PerspectiveCamera(
camera_view_point=mdl.Point(x=-48.974571228027344, y=-64.20051574707031, z=10.666350364685059),
camera_direction=mdl.Direction(x=0.7232745289802551, y=0.5967116951942444, z=-0.3475759029388428),
camera_up_vector=mdl.Direction(x=0.27662187814712524, y=0.21082592010498047, z=0.937567412853241),
field_of_view=60,
),
guid="81daa431-bf01-4a49-80a2-1ab07c177717",
)
assert viewpoint.visualization_info == expected_vp
assert viewpoint.snapshot is not None
def assert_files_present(saved_bcf_path, expected_files):
with zipfile.ZipFile(saved_bcf_path) as bcf_zip:
for file_path in expected_files:
assert zipfile.Path(bcf_zip, file_path).exists()
Binary file not shown.
View File
+129
View File
@@ -0,0 +1,129 @@
"""BCF XML tests."""
import uuid
from pathlib import Path
from tempfile import TemporaryDirectory
import pytest
import bcf.v3.model as mdl
from bcf.v3.bcfxml import BcfXml
from bcf.v3.topic import TopicHandler
from bcf.v3.visinfo import (
VisualizationInfoHandler,
build_camera_from_vectors,
build_components,
)
from bcf.xml_parser import XmlParserSerializer
@pytest.fixture()
def build_sample(xml_handler: XmlParserSerializer) -> tuple[BcfXml, TopicHandler]:
ext = mdl.Extensions(topic_types=mdl.ExtensionsTopicTypes(topic_type=["Test type"]))
bcf = BcfXml.create_new("Test project", extensions=ext, xml_handler=xml_handler)
orig_th = bcf.add_topic("Test topic", "Test message", "Test author", "Test type")
return bcf, orig_th
def test_bcf_roundtrip(xml_handler, build_sample) -> None:
"""Saving and loading a bcf xml project returns the same objects."""
bcf, orig_th = build_sample
with TemporaryDirectory() as tmp_dir:
file_path = Path(tmp_dir) / "test.bcf"
bcf.save(file_path)
with BcfXml.load(file_path, xml_handler=xml_handler) as parsed:
assert parsed == bcf
parsed_th = parsed.topics[orig_th.guid]
assert parsed_th == orig_th
def test_bcf_edit_saveas(xml_handler, build_sample) -> None:
"""Saving and loading a bcf xml project returns the same objects."""
bcf, orig_th = build_sample
with TemporaryDirectory() as tmp_dir:
file_path = Path(tmp_dir) / "test.bcf"
bcf.save(file_path)
with BcfXml.load(file_path, xml_handler=xml_handler) as parsed:
for th in parsed.topics.values():
th.topic.title = "New Topic Title"
modified_path = Path(tmp_dir) / "edited.bcf"
parsed.save(modified_path)
with BcfXml.load(modified_path, xml_handler=xml_handler) as modified_parsed:
assert modified_parsed == bcf
parsed_th = modified_parsed.topics[orig_th.guid]
assert parsed_th.markup != orig_th.markup
assert parsed_th.markup == parsed.topics[orig_th.guid].markup
assert parsed_th.viewpoints == orig_th.viewpoints
assert parsed_th.bim_snippet == orig_th.bim_snippet
def test_bcf_edit(xml_handler, build_sample) -> None:
"""Saving and loading a bcf xml project returns the same objects."""
bcf, orig_th = build_sample
with TemporaryDirectory() as tmp_dir:
file_path = Path(tmp_dir) / "test.bcf"
bcf.save(file_path)
with BcfXml.load(file_path, xml_handler=xml_handler) as parsed:
for th in parsed.topics.values():
th.topic.title = "New Topic Title"
parsed.save()
with BcfXml.load(file_path, xml_handler=xml_handler) as modified_parsed:
assert modified_parsed == bcf
assert len(modified_parsed.topics) == 1
parsed_th = modified_parsed.topics[orig_th.guid]
assert parsed_th.markup != orig_th.markup
assert parsed_th.markup == parsed.topics[orig_th.guid].markup
assert parsed_th.viewpoints == orig_th.viewpoints
assert parsed_th.bim_snippet == orig_th.bim_snippet
def test_save_no_filename(build_sample) -> None:
bcf, _ = build_sample
with pytest.raises(ValueError):
bcf.save()
def test_load_no_filename() -> None:
with pytest.raises(ValueError):
BcfXml.load("")
def test_save_keep_open(build_sample) -> None:
bcf, orig_th = build_sample
with TemporaryDirectory() as tmp_dir:
file_path = Path(tmp_dir) / "test.bcf"
bcf.save(file_path, keep_open=True)
assert bcf._zip_file is not None
bcf._zip_file.close()
# image = PIL.Image.new('RGB', size=(100, 100))
# file = BinaryIO()
# image.save(file)
def test_massive_bcf(xml_handler) -> None:
ext = mdl.Extensions(topic_types=mdl.ExtensionsTopicTypes(topic_type=["Test type"]))
bcf = BcfXml.create_new("Test project", extensions=ext, xml_handler=xml_handler)
for i in range(100):
th = bcf.add_topic(f"Topic {i:04}", f"Message {i:04}", "Test author", "Test type")
vi = mdl.VisualizationInfo(
guid=str(uuid.uuid4()),
components=build_components(str(uuid.uuid4())),
perspective_camera=build_camera_from_vectors([i, 0, 0], [0, 1, 0], [0, 0, 1]),
)
vh = VisualizationInfoHandler(visualization_info=vi, xml_handler=xml_handler)
th.add_visinfo_handler(vh)
assert len(bcf.topics) == 100
with TemporaryDirectory() as tmp_dir:
file_path = Path(tmp_dir) / "test.bcf"
bcf.save(file_path)
def test_equality_with_wrong_object() -> None:
with pytest.raises(TypeError):
build_sample[0] == "Wrong object"
def test_topic_equality_with_wrong_object() -> None:
with pytest.raises(TypeError):
build_sample[1] == "Wrong object"
+230
View File
@@ -0,0 +1,230 @@
import json
import os
import zipfile
from pathlib import Path
from xsdata.models.datatype import XmlDateTime
import bcf.v3.model as mdl
from bcf.v3.bcfxml import BcfXml
from bcf.v3.topic import TopicHandler
def test_doc_ref_internal() -> None:
"""
All the info in a BCF is parsed correctly.
Uses sample file from https://github.com/buildingSMART/BCF-XML/
"""
bcf_path = Path(__file__).parent / "Document reference internal.bcf"
with BcfXml.load(bcf_path) as bcf:
assert_everything_in_place(bcf)
def test_doc_ref_internal_save() -> None:
base_dir = Path(__file__).parent
bcf_path = base_dir / "Document reference internal.bcf"
target_path = base_dir / "Document reference internal Saved.bcf"
with BcfXml.load(bcf_path) as bcf:
bcf.save(target_path)
assert target_path.exists()
with BcfXml.load(target_path) as bcf2:
assert_everything_in_place(bcf2)
assert_files_present(target_path)
os.unlink(target_path)
def assert_everything_in_place(bcf: BcfXml):
assert bcf.version.version_id == "3.0"
assert bcf.project.name == "BCF 3.0 test cases"
assert bcf.project.project_id == "de894a86-3a08-4ea0-b2d1-6c222b5602d1"
assert len(bcf.topics) == 1
topic_handler = bcf.topics["8ac9822a-761a-4deb-9f39-f61286acbf6a"]
expected_h1 = mdl.Header(
files=mdl.HeaderFiles(
file=[
mdl.File(
filename="BCF-ARK",
date=XmlDateTime(2021, 1, 4, 9, 37, 45),
reference="https://bimsync.com/project/f5fbb3c695274d1890036bf64f77eb71/revisions/007afab57f264d2296aae0a452486ae1",
is_external=True,
),
mdl.File(
filename="BCF-MEP",
date=XmlDateTime(2017, 8, 7, 9, 51, 34),
reference="https://bimsync.com/project/f5fbb3c695274d1890036bf64f77eb71/revisions/a21ed391f9e046a2bb2bc879c48f1d48",
is_external=True,
),
]
)
)
assert topic_handler.header == expected_h1
expected_comments = [
mdl.Comment(
date=XmlDateTime(2021, 2, 17, 9, 16, 4, 160_000_000),
author="Architect@example.com",
comment="A comment",
viewpoint=mdl.CommentViewpoint(guid="20ad2ff7-ceac-4d4b-b288-ad92a0f65182"),
guid="c045ef04-324d-4c36-9ac8-831f2a15c6d6",
),
]
assert topic_handler.comments == expected_comments
expected_viewpoints = [
mdl.ViewPoint(
viewpoint="Viewpoint_20ad2ff7-ceac-4d4b-b288-ad92a0f65182.bcfv",
snapshot="Snapshot_20ad2ff7-ceac-4d4b-b288-ad92a0f65182.png",
guid="20ad2ff7-ceac-4d4b-b288-ad92a0f65182",
),
]
expected_topic = mdl.Topic(
reference_links=mdl.TopicReferenceLinks(),
title="Document Reference Internal",
labels=mdl.TopicLabels(),
creation_date=XmlDateTime(2021, 2, 17, 9, 16, 4, 160_000_000),
creation_author="Architect@example.com",
modified_date=XmlDateTime(2021, 2, 17, 9, 16, 4, 160_000_000),
assigned_to="OtherUser@doe.com",
document_references=mdl.TopicDocumentReferences(
document_reference=[
mdl.DocumentReference(
document_guid="b1d1b7f0-60b9-457d-ad12-16e0fb997bc5",
description="ThisIsADocument.txt",
guid="048e898f-555f-47c3-a273-6c664fb2ef69",
),
],
),
related_topics=mdl.TopicRelatedTopics(),
comments=mdl.TopicComments(comment=expected_comments),
viewpoints=mdl.TopicViewpoints(view_point=expected_viewpoints),
guid="8ac9822a-761a-4deb-9f39-f61286acbf6a",
server_assigned_id="6",
topic_type="ERROR",
topic_status="OPEN",
)
assert topic_handler.topic == expected_topic
expected_m1 = mdl.Markup(
header=expected_h1,
topic=expected_topic,
)
assert topic_handler.markup == expected_m1
assert topic_handler.bim_snippet is None
assert_viewpoints(topic_handler.viewpoints)
def assert_viewpoints(viewpoints):
assert len(viewpoints) == 1
expected_vp = mdl.VisualizationInfo(
components=mdl.Components(
selection=mdl.ComponentSelection(),
visibility=mdl.ComponentVisibility(
view_setup_hints=mdl.ViewSetupHints(
spaces_visible=False,
space_boundaries_visible=False,
openings_visible=False,
),
exceptions=mdl.ComponentVisibilityExceptions(
component=[
mdl.Component(ifc_guid="1m5wAJelDFdhn6qBdOGjos", originating_system="https://bimsync.com"),
mdl.Component(ifc_guid="1bbI761TbBCOoIa5Kt6PXt", originating_system="https://bimsync.com"),
]
),
default_visibility=True,
),
coloring=mdl.ComponentColoring(),
),
perspective_camera=mdl.PerspectiveCamera(
camera_view_point=mdl.Point(x=23.112083480037754, y=-25.45574897560043, z=18.52519828443092),
camera_direction=mdl.Direction(x=-0.6289237666876837, y=0.5933487270610425, z=-0.5023864884632315),
camera_up_vector=mdl.Direction(x=-0.36542566067664123, y=0.3447553774917179, z=0.8646431727652648),
field_of_view=60,
aspect_ratio=1,
),
lines=mdl.VisualizationInfoLines(),
clipping_planes=mdl.VisualizationInfoClippingPlanes(),
bitmaps=mdl.VisualizationInfoBitmaps(),
guid="20ad2ff7-ceac-4d4b-b288-ad92a0f65182",
)
viewpoint = viewpoints["Viewpoint_20ad2ff7-ceac-4d4b-b288-ad92a0f65182.bcfv"]
assert viewpoint.visualization_info == expected_vp
assert viewpoint.snapshot is not None
def assert_second_viewpoint(viewpoint, expected_selection, expected_exception, expected_coloring) -> None:
expected_vp = mdl.VisualizationInfo(
components=mdl.Components(
view_setup_hints=mdl.ViewSetupHints(
spaces_visible=False,
space_boundaries_visible=False,
openings_visible=False,
),
selection=expected_selection,
visibility=mdl.ComponentVisibility(
exceptions=expected_exception,
default_visibility=False,
),
coloring=expected_coloring,
),
perspective_camera=mdl.PerspectiveCamera(
camera_view_point=mdl.Point(x=-47.18794250488281, y=43.829200744628906, z=10.666350364685059),
camera_direction=mdl.Direction(x=0.6745243072509766, y=-0.6599355936050415, z=-0.33091068267822266),
camera_up_vector=mdl.Direction(x=0.2271970510482788, y=-0.24091780185699463, z=0.9435783624649048),
field_of_view=60,
),
guid="21dd4807-e9af-439e-a980-04d913a6b1ce",
)
assert viewpoint.visualization_info == expected_vp
assert viewpoint.snapshot is not None
def assert_third_viewpoint(viewpoint, expected_selection, expected_exception, expected_coloring) -> None:
expected_vp = mdl.VisualizationInfo(
components=mdl.Components(
view_setup_hints=mdl.ViewSetupHints(
spaces_visible=False,
space_boundaries_visible=False,
openings_visible=True,
),
selection=expected_selection,
visibility=mdl.ComponentVisibility(
exceptions=expected_exception,
default_visibility=True,
),
coloring=expected_coloring,
),
perspective_camera=mdl.PerspectiveCamera(
camera_view_point=mdl.Point(x=-48.974571228027344, y=-64.20051574707031, z=10.666350364685059),
camera_direction=mdl.Direction(x=0.7232745289802551, y=0.5967116951942444, z=-0.3475759029388428),
camera_up_vector=mdl.Direction(x=0.27662187814712524, y=0.21082592010498047, z=0.937567412853241),
field_of_view=60,
),
guid="81daa431-bf01-4a49-80a2-1ab07c177717",
)
assert viewpoint.visualization_info == expected_vp
assert viewpoint.snapshot is not None
def assert_files_present(saved_bcf_path):
expected_files = [
"bcf.version",
"project.bcfp",
"documents.xml",
"extensions.xml",
"documents/b1d1b7f0-60b9-457d-ad12-16e0fb997bc5",
"8ac9822a-761a-4deb-9f39-f61286acbf6a/markup.bcf",
"8ac9822a-761a-4deb-9f39-f61286acbf6a/Viewpoint_20ad2ff7-ceac-4d4b-b288-ad92a0f65182.bcfv",
"8ac9822a-761a-4deb-9f39-f61286acbf6a/Snapshot_20ad2ff7-ceac-4d4b-b288-ad92a0f65182.png",
]
with zipfile.ZipFile(saved_bcf_path) as bcf_zip:
for file_path in expected_files:
assert zipfile.Path(bcf_zip, file_path).exists()
+135 -15
View File
@@ -16,6 +16,24 @@
# You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
PYTHON:=python
PATCH:=patch
SED:=sed -i
ifeq ($(OS),Windows_NT)
HOSTOS:=win
else
UNAME_S:=$(shell uname -s)
ifeq ($(UNAME_S),Linux)
HOSTOS:=linux
endif
ifeq ($(UNAME_S),Darwin)
HOSTOS:=macos
PYTHON:=python3
PATCH:=patch -d
SED:=sed -i '' -e
endif
endif
VERSION:=`date '+%y%m%d'`
PYVERSION:=py37
@@ -29,7 +47,7 @@ PYNUMBER:=39
endif
ifeq ($(PYVERSION), py310)
PYLIBDIR:=python3.10
PYNUMBER:=31
PYNUMBER:=310
endif
ifeq ($(PLATFORM), linux)
@@ -38,18 +56,24 @@ HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.7.5/download/linux-64/hpp
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/2.6.5/download/linux-64/eigenpy-2.6.5-py37h95e2c48_0.tar.bz2
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.74.0/download/linux-64/boost-1.74.0-py37h0379df6_3.tar.bz2
LXML_URL:=https://files.pythonhosted.org/packages/30/c0/d0526314971fc661b083ab135747dc68446a3022686da8c16d25fcf6ef07/lxml-4.6.3-cp37-cp37m-manylinux2014_x86_64.whl
SHAPELY_URL:=https://files.pythonhosted.org/packages/a2/73/07f507b539527579c026ed5f411c35e287685ce794c4dfb7edf6675cdc24/Shapely-1.8.4-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl
PILLOW_URL:=https://files.pythonhosted.org/packages/ed/d5/c2e84e1e36ab8ebea033921d5886a056c77e18bab5ab1051fcc22de2e8a2/Pillow-9.2.0-cp37-cp37m-manylinux_2_28_x86_64.whl
endif
ifeq ($(PYVERSION), py39)
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.7.5/download/linux-64/hpp-fcl-1.7.5-py39hbcdfc36_0.tar.bz2
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/2.6.5/download/linux-64/eigenpy-2.6.5-py39h5aed9d1_0.tar.bz2
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.74.0/download/linux-64/boost-1.74.0-py39h5472131_3.tar.bz2
LXML_URL:=https://files.pythonhosted.org/packages/19/d9/a69c6aff5673554df48120565a14a50eaa41d29ae03b02faa0b023666318/lxml-4.6.3-cp39-cp39-manylinux2014_x86_64.whl
SHAPELY_URL:=https://files.pythonhosted.org/packages/08/12/5af915b2bc0470f4a652ef9978557a0fa8b705b122ccaec6626029e1674c/Shapely-1.8.4-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl
PILLOW_URL:=https://files.pythonhosted.org/packages/01/61/3ff85fb4bb596ce3d223c8fcf93c8df5c12bc8899dfb4fb3cb1c5b20dd5f/Pillow-9.2.0-cp39-cp39-manylinux_2_28_x86_64.whl
endif
ifeq ($(PYVERSION), py310)
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.8.0/download/linux-64/hpp-fcl-1.8.0-py310hdaf7e41_1.tar.bz2
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/2.6.11/download/linux-64/eigenpy-2.6.11-py310hf3e5c9c_0.tar.bz2
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.74.0/download/linux-64/boost-1.74.0-py310h7c3ba0c_5.tar.bz2
LXML_URL:=https://files.pythonhosted.org/packages/25/1e/19b46d8e8881fe0df2e20945d51919eeb1817836d62a90efa8506530e45c/lxml-4.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl
SHAPELY_URL:=https://files.pythonhosted.org/packages/60/20/c6321e3da92a4ad9495727c9aa8c423e47295572ccd35ece82d1ac013e28/Shapely-1.8.4-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl
PILLOW_URL:=https://files.pythonhosted.org/packages/f6/51/320986ebd6d46a0e95c2240468ced73153b691ce07617078bcdf30c609ec/Pillow-9.2.0-cp310-cp310-manylinux_2_28_x86_64.whl
endif
ASSIMP_URL:=https://anaconda.org/conda-forge/assimp/5.0.1/download/linux-64/assimp-5.0.1-hedfc422_6.tar.bz2
OCTOMAP_URL:=https://anaconda.org/conda-forge/octomap/1.9.7/download/linux-64/octomap-1.9.7-h4bd325d_0.tar.bz2
@@ -62,48 +86,98 @@ HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.7.5/download/osx-64/hpp-f
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/2.6.5/download/osx-64/eigenpy-2.6.5-py37h0695097_0.tar.bz2
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.74.0/download/osx-64/boost-1.74.0-py37hd79e0ac_3.tar.bz2
LXML_URL:=https://files.pythonhosted.org/packages/1e/3e/f0abc15d5dac50939bccc589aae336d5ead4c72e7ad1039a2e0f3630ea92/lxml-4.6.3-cp37-cp37m-macosx_10_9_x86_64.whl
SHAPELY_URL:=https://files.pythonhosted.org/packages/47/a9/7c644e71638bff2bbaa87f05ee62fdf6b4552a55e74bbf731d6210b6d436/Shapely-1.8.4-cp37-cp37m-macosx_10_9_x86_64.whl
PILLOW_URL:=https://files.pythonhosted.org/packages/88/49/c26fc3b5b0e82bdc9d8751d6b939da29327b0d98f7c3b95a575cbfed2743/Pillow-9.2.0-cp37-cp37m-macosx_10_10_x86_64.whl
endif
ifeq ($(PYVERSION), py39)
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.7.5/download/osx-64/hpp-fcl-1.7.5-py39h1e32b98_0.tar.bz2
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/2.6.5/download/osx-64/eigenpy-2.6.5-py39h5405915_0.tar.bz2
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.74.0/download/osx-64/boost-1.74.0-py39ha641261_3.tar.bz2
LXML_URL:=https://files.pythonhosted.org/packages/b8/74/a71f7ad72e8db54ce899efab84507b801660750cbbfa6a39e6717557d36a/lxml-4.6.3-cp39-cp39-macosx_10_9_x86_64.whl
SHAPELY_URL:=https://files.pythonhosted.org/packages/f5/f9/bf6cc34b02b3c7d3777c5e47fa57d2f69387613bbd8decdf3577a9d83a83/Shapely-1.8.4-cp39-cp39-macosx_10_9_x86_64.whl
PILLOW_URL:=https://files.pythonhosted.org/packages/88/7a/ddfe28b485b623361457d4783007c1f9ba83a87f93e7fec32f64793efb6c/Pillow-9.2.0-cp39-cp39-macosx_10_10_x86_64.whl
endif
ifeq ($(PYVERSION), py310)
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.8.0/download/osx-64/hpp-fcl-1.8.0-py310h651ac30_1.tar.bz2
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/2.6.11/download/osx-64/eigenpy-2.6.11-py310hc03097c_0.tar.bz2
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.74.0/download/osx-64/boost-1.74.0-py310h509978a_5.tar.bz2
LXML_URL:=https://files.pythonhosted.org/packages/a1/44/17b7dac7a18807d30e2fe10c3328c152808f5464565e230bfd0e77f178c6/lxml-4.8.0-cp310-cp310-macosx_10_15_x86_64.whl
SHAPELY_URL:=https://files.pythonhosted.org/packages/f5/a8/02347c450b5507b9a7a2c15ad6a029613149468fc55ac2100ab215666159/Shapely-1.8.4-cp310-cp310-macosx_10_9_x86_64.whl
PILLOW_URL:=https://files.pythonhosted.org/packages/d8/60/b13c00d403f34110e96c1b5c0afa73ce461efe3fe960c3a7e3e7fe190d82/Pillow-9.2.0-cp310-cp310-macosx_10_10_x86_64.whl
endif
ASSIMP_URL:=https://anaconda.org/conda-forge/assimp/5.0.1/download/osx-64/assimp-5.0.1-h1224e73_6.tar.bz2
OCTOMAP_URL:=https://anaconda.org/conda-forge/octomap/1.9.7/download/osx-64/octomap-1.9.7-h940c156_0.tar.bz2
ZLIB_URL:=https://anaconda.org/conda-forge/zlib/1.2.11/download/osx-64/zlib-1.2.11-h7795811_1010.tar.bz2
endif
ifeq ($(PLATFORM), macosm1)
ifeq ($(PYVERSION), py37)
# Warning: py37 not supported on Apple M1
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.7.5/download/osx-64/hpp-fcl-1.7.5-py37h2d7f23a_0.tar.bz2
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/2.6.5/download/osx-64/eigenpy-2.6.5-py37h0695097_0.tar.bz2
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.74.0/download/osx-64/boost-1.74.0-py37hd79e0ac_3.tar.bz2
LXML_URL:=https://anaconda.org/conda-forge/lxml/4.9.1/download/osx-64/lxml-4.9.1-py37h994c40b_0.tar.bz2
SHAPELY_URL:=https://files.pythonhosted.org/packages/47/a9/7c644e71638bff2bbaa87f05ee62fdf6b4552a55e74bbf731d6210b6d436/Shapely-1.8.4-cp37-cp37m-macosx_10_9_x86_64.whl
PILLOW_URL:=https://files.pythonhosted.org/packages/aa/bc/21097cd891dd2fa02f2b3d767e02e883e026482e59d29975d1bc30024aa3/Pillow-9.2.0-cp39-cp39-macosx_11_0_arm64.whl
endif
ifeq ($(PYVERSION), py39)
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.7.5/download/osx-arm64/hpp-fcl-1.7.5-py39ha69f3c1_2.tar.bz2
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/2.6.7/download/osx-arm64/eigenpy-2.6.7-py39ha69f3c1_0.tar.bz2
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.78.0/download/osx-arm64/boost-1.78.0-py39h2550fe3_0.tar.bz2
LXML_URL:=https://anaconda.org/conda-forge/lxml/4.9.1/download/osx-arm64/lxml-4.9.1-py39h9eb174b_0.tar.bz2
SHAPELY_URL:=https://files.pythonhosted.org/packages/8e/60/8186995a96c5a27c9b79c015bbdd89c6d9a6df9988f86f3f1027ecd4a56a/Shapely-1.8.4-cp39-cp39-macosx_11_0_arm64.whl
PILLOW_URL:=https://files.pythonhosted.org/packages/aa/bc/21097cd891dd2fa02f2b3d767e02e883e026482e59d29975d1bc30024aa3/Pillow-9.2.0-cp39-cp39-macosx_11_0_arm64.whl
endif
ifeq ($(PYVERSION), py310)
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.8.0/download/osx-arm64/hpp-fcl-1.8.0-py310h5699539_2.tar.bz2
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/2.6.9/download/osx-arm64/eigenpy-2.6.9-py310habfc766_1.tar.bz2
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.78.0/download/osx-arm64/boost-1.78.0-py310h4566fd1_0.tar.bz2
LXML_URL:=https://anaconda.org/conda-forge/lxml/4.9.1/download/osx-arm64/lxml-4.9.1-py310h02f21da_0.tar.bz2
SHAPELY_URL:=https://files.pythonhosted.org/packages/ba/d0/75cd36c81a1aa26dd6d37cf7ae1c79b0bc2d379e0f32c359bcb00b14e5c3/Shapely-1.8.4-cp310-cp310-macosx_11_0_arm64.whl
PILLOW_URL:=https://files.pythonhosted.org/packages/0c/5f/117b653cad585f3aedfe0de996c292e67d4b020ed77f652e5a6c8c24f908/Pillow-9.2.0-cp310-cp310-macosx_11_0_arm64.whl
endif
ASSIMP_URL:=https://anaconda.org/conda-forge/assimp/5.0.1/download/osx-arm64/assimp-5.0.1-h0f81e16_7.tar.bz2
OCTOMAP_URL:=https://anaconda.org/conda-forge/octomap/1.9.7/download/osx-arm64/octomap-1.9.7-hc021e02_0.tar.bz2
ZLIB_URL:=https://anaconda.org/conda-forge/zlib/1.2.11/download/osx-arm64/zlib-1.2.11-h90dfc92_1014.tar.bz2
endif
ifeq ($(PLATFORM), win)
ifeq ($(PYVERSION), py37)
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.7.5/download/win-64/hpp-fcl-1.7.5-py37h839d6b1_0.tar.bz2
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/2.6.5/download/win-64/eigenpy-2.6.5-py37h2c32e34_0.tar.bz2
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.74.0/download/win-64/boost-1.74.0-py37h3b38789_3.tar.bz2
LXML_URL:=https://files.pythonhosted.org/packages/9e/5e/171ee9d40a600f565fe691ec5bf7596247ec62cfb2edc00c91afe8ea837b/lxml-4.6.3-cp37-cp37m-win_amd64.whl
SHAPELY_URL:=https://files.pythonhosted.org/packages/ca/4d/85b912d7947261e6b3218b01946bcd7c2efb2bbafaf517c6fe1dc3bb8399/Shapely-1.8.4-cp37-cp37m-win_amd64.whl
PILLOW_URL:=https://files.pythonhosted.org/packages/69/f5/9e802159d78b2eaf26bf1f8b94648605993f5ca7247ac8870f065063fc40/Pillow-9.2.0-cp37-cp37m-win_amd64.whl
endif
ifeq ($(PYVERSION), py39)
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.7.5/download/win-64/hpp-fcl-1.7.5-py39h2e7c763_0.tar.bz2
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/2.6.5/download/win-64/eigenpy-2.6.5-py39h3ce40e6_0.tar.bz2
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.74.0/download/win-64/boost-1.74.0-py39hefe7e4c_3.tar.bz2
LXML_URL:=https://files.pythonhosted.org/packages/72/d4/426ecb8849c47c3e370c87aa0ac05d85768df917ffea27fcd6686a5e6495/lxml-4.6.3-cp39-cp39-win_amd64.whl
SHAPELY_URL:=https://files.pythonhosted.org/packages/72/c3/0d52812db25ca71203f6203891277e6ee4f0244b21a36c062123ece15f36/Shapely-1.8.4-cp39-cp39-win_amd64.whl
PILLOW_URL:=https://files.pythonhosted.org/packages/19/3f/b4d4bcf05dbcbe07f2e9613a8f4180c297395e73a91d8ad22c32c6624f8c/Pillow-9.2.0-cp39-cp39-win_amd64.whl
endif
ifeq ($(PYVERSION), py310)
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.8.0/download/win-64/hpp-fcl-1.8.0-py310hc5a3c62_1.tar.bz2
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/2.6.11/download/win-64/eigenpy-2.6.11-py310hbd43d28_0.tar.bz2
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.74.0/download/win-64/boost-1.74.0-py310hc781a3c_5.tar.bz2
LXML_URL:=https://files.pythonhosted.org/packages/f6/71/65c80a4caa1617a4c6e8fe1500cebb179db96232e2f623bfe6a1f4294e39/lxml-4.8.0-cp310-cp310-win_amd64.whl
SHAPELY_URL:=https://files.pythonhosted.org/packages/24/b5/46d13799804b5fe9ede2eca8b19023410e29b78baca0bff727671a1df8c1/Shapely-1.8.4-cp310-cp310-win_amd64.whl
PILLOW_URL:=https://files.pythonhosted.org/packages/02/55/67a3c17b9e7d972ed8c246f104da99ca4f3ea42fba566697e479011b84b6/Pillow-9.2.0-cp310-cp310-win_amd64.whl
endif
ASSIMP_URL:=https://anaconda.org/conda-forge/assimp/5.0.1/download/win-64/assimp-5.0.1-hc2aa0de_6.tar.bz2
OCTOMAP_URL:=https://anaconda.org/conda-forge/octomap/1.9.7/download/win-64/octomap-1.9.7-h5362a0b_0.tar.bz2
ZLIB_URL:=https://anaconda.org/conda-forge/zlib/1.2.11/download/win-64/zlib-1.2.11-h62dcd97_1010.tar.bz2
endif
.PHONY: bump
bump:
cd . && $(SED) "s/$(OLD)/$(NEW)/" Makefile
cd ../ifcopenshell-python/ && $(SED) "s/$(OLD)/$(NEW)/" Makefile
cd ../ifcopenshell-python/docs/ifcconvert/ && $(SED) "s/$(OLD)/$(NEW)/" installation.rst
cd ../ifcopenshell-python/docs/ifcopenshell-python/ && $(SED) "s/$(OLD)/$(NEW)/" installation.rst
.PHONY: dist
dist:
ifndef PLATFORM
@@ -115,7 +189,7 @@ endif
cp -r blenderbim/* dist/blenderbim/
# Provides IfcOpenShell Python functionality
cd dist/working && wget https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-$(PYNUMBER)-v0.7.0-cb8cfd7-$(PLATFORM)64.zip
cd dist/working && wget https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-$(PYNUMBER)-v0.7.0-6180d73-$(PLATFORM)64.zip
cd dist/working && unzip ifcopenshell-python*
cp -r dist/working/ifcopenshell dist/blenderbim/libs/site/packages/
@@ -138,11 +212,13 @@ endif
# Provides IFCClash functionality
cp -r dist/working/IfcOpenShell-0.7.0/src/ifcclash/ifcclash dist/blenderbim/libs/site/packages/
# Provides BIMTester functionality
cd dist/working && python -m venv env
cd dist/working && $(PYTHON) -m venv env
cd dist/working && . env/bin/activate && pip install pybabel
cd dist/working && . env/bin/activate && pip install babel
cd dist/working && . env/bin/activate && ./env/bin/pybabel compile -d ./IfcOpenShell-0.7.0/src/ifcbimtester/bimtester/locale/
cp -r dist/working/IfcOpenShell-0.7.0/src/ifcbimtester/bimtester dist/blenderbim/libs/site/packages/
# Provides IFCTester functionality
cp -r dist/working/IfcOpenShell-0.7.0/src/ifctester/ifctester dist/blenderbim/libs/site/packages/
# Provides IFCCOBie functionality
cp -r dist/working/IfcOpenShell-0.7.0/src/ifccobie/* dist/blenderbim/libs/site/packages/
# Provides IFCDiff functionality
@@ -161,9 +237,9 @@ endif
# Provides Mustache templating in construction documentation
mkdir dist/working
cd dist/working && wget https://files.pythonhosted.org/packages/d6/fd/eb8c212053addd941cc90baac307c00ac246ac3fce7166b86434c6eae963/pystache-0.5.4.tar.gz
cd dist/working && wget https://files.pythonhosted.org/packages/3f/e7/8750ba6c6101d6aa5ceeb20c013adf2c6f3554a12c71d75654b468404bfa/pystache-0.6.0.tar.gz
cd dist/working && tar -xzvf pystache*
cd dist/working/pystache-0.5.4/ && python setup.py build && cp -r build/lib/pystache ../../blenderbim/libs/site/packages/
cd dist/working/pystache-0.6.0/ && $(PYTHON) setup.py build && cp -r build/lib/pystache ../../blenderbim/libs/site/packages/
rm -rf dist/working
# Provides SVG export in construction documentation
@@ -238,16 +314,23 @@ endif
# Required by bcf
mkdir dist/working
cd dist/working && wget https://files.pythonhosted.org/packages/bb/41/ad9ce53bb978b68af8ae415293cafc89b165b8ad55a593725299dca76729/xmlschema-1.1.1.tar.gz
cd dist/working && wget https://files.pythonhosted.org/packages/5b/ef/f97c3e1a7efa00e989a793fe15297214fc95ad7d9e3810586bd08ce9f0f3/xmlschema-2.0.2.tar.gz
cd dist/working && tar -xzvf xmlschema*
cp -r dist/working/xmlschema-1.1.1/xmlschema dist/blenderbim/libs/site/packages/
cp -r dist/working/xmlschema-2.0.2/xmlschema dist/blenderbim/libs/site/packages/
rm -rf dist/working
# Required by bcf
mkdir dist/working
cd dist/working && wget https://files.pythonhosted.org/packages/12/f9/f9960222d5274944b01391749e55e4dcdf28d8f0c108b64ac931ceff6fdb/elementpath-1.4.3.tar.gz
cd dist/working && wget https://files.pythonhosted.org/packages/ad/c7/17c9d16320d8e2cfdb27d2fd298b5e8f7a3f211025b0c1bc7a39a85bd690/xsdata-22.11.tar.gz
cd dist/working && tar -xzvf xsdata*
cp -r dist/working/xsdata-22.11/xsdata dist/blenderbim/libs/site/packages/
rm -rf dist/working
# Required by bcf
mkdir dist/working
cd dist/working && wget https://files.pythonhosted.org/packages/11/bc/5afb61dd5d863e5cf77cd952445c50c17e65953405986f19e97e4389692a/elementpath-3.0.2.tar.gz
cd dist/working && tar -xzvf elementpath*
cp -r dist/working/elementpath-1.4.3/elementpath dist/blenderbim/libs/site/packages/
cp -r dist/working/elementpath-3.0.2/elementpath dist/blenderbim/libs/site/packages/
rm -rf dist/working
# Required by bcf
@@ -271,6 +354,29 @@ endif
cp -r dist/working/PyP6Xer-1.13.0/xerparser dist/blenderbim/libs/site/packages/
rm -rf dist/working
# Required by QTOCalculator
mkdir dist/working
cd dist/working && wget $(SHAPELY_URL)
cd dist/working && cp *.whl shapely.zip && unzip shapely.zip
cp -r dist/working/shapely dist/blenderbim/libs/site/packages/
ifeq ($(PLATFORM), win)
cp -r dist/working/Shapely.libs dist/blenderbim/libs/site/packages/
endif
ifeq ($(PLATFORM), linux)
cp -r dist/working/Shapely.libs dist/blenderbim/libs/site/packages/
endif
rm -rf dist/working
# Required by the BIM tool thumbnail generator
mkdir dist/working
cd dist/working && wget $(PILLOW_URL)
cd dist/working && cp *.whl pillow.zip && unzip pillow.zip
cp -r dist/working/PIL dist/blenderbim/libs/site/packages/
ifeq ($(PLATFORM), linux)
cp -r dist/working/Pillow.libs dist/blenderbim/libs/site/packages/
endif
rm -rf dist/working
# Required by xerparser and IFC4D
# TODO: remove this dependency. It's only used to show a progress bar.
mkdir dist/working
@@ -281,7 +387,7 @@ endif
# Required by IFCCityJSON
mkdir dist/working
cd dist/working && python -m venv env
cd dist/working && $(PYTHON) -m venv env
cd dist/working && mkdir site-packages
cd dist/working && . env/bin/activate && pip install cjio --target=./site-packages
cd dist/working/site-packages/ && rm -r *dist-info*
@@ -389,16 +495,30 @@ endif
# See bug #1294
cd dist/working/ && wget https://raw.githubusercontent.com/IfcOpenShell/IfcOpenShell/v0.7.0/src/ifcbimtester/patch/model_core.patch
cd dist/working/ && wget https://raw.githubusercontent.com/IfcOpenShell/IfcOpenShell/v0.7.0/src/ifcbimtester/patch/runner_util.patch
cd dist/working/ && patch ../blenderbim/libs/site/packages/behave/model_core.py < model_core.patch
cd dist/working/ && patch ../blenderbim/libs/site/packages/behave/runner_util.py < runner_util.patch
cd dist/working/ && $(PATCH) ../blenderbim/libs/site/packages/behave/model_core.py < model_core.patch
cd dist/working/ && $(PATCH) ../blenderbim/libs/site/packages/behave/runner_util.py < runner_util.patch
# See bug #2159
cd dist/working/ && wget https://raw.githubusercontent.com/IfcOpenShell/IfcOpenShell/v0.7.0/src/ifcbimtester/patch/junit.patch
cd dist/working/ && $(PATCH) ../blenderbim/libs/site/packages/behave/reporter/junit.py < junit.patch
rm -rf dist/working
# Required by ids - uh, not any more, but I'm trying it out in the drawing module
ifeq ($(PLATFORM), macosm1)
# No wheels available for MacOS M1, but turns out this Anaconda build works
mkdir dist/working
cd dist/working && wget $(LXML_URL)
cd dist/working && tar -xf lxml*
cp -r dist/working/lib/$(PYLIBDIR)/site-packages/lxml dist/blenderbim/libs/site/packages/
rm -rf dist/working
else
# Wheels are preferred for other OSes. A first attempt at using Anaconda
# builds for everything shows it breaks on windows. See #2422.
mkdir dist/working
cd dist/working && wget $(LXML_URL)
cd dist/working && cp *.whl lxml.zip && unzip lxml.zip
cp -r dist/working/lxml dist/blenderbim/libs/site/packages/
rm -rf dist/working
endif
# Required by behave
mkdir dist/working
@@ -470,14 +590,14 @@ endif
cp -r dist/working/IFC2JSON_python-master/file_converters/ifcjson dist/blenderbim/libs/site/packages/
rm -rf dist/working
# Provides OpenLCA fucntionality
# Provides OpenLCA functionality
mkdir dist/working
cd dist/working && wget https://files.pythonhosted.org/packages/85/33/f91b96e9e8608ff65a003b692e8a9cdd60f2178f60617e5b1d21334c009c/olca-ipc-0.0.10.tar.gz
cd dist/working && tar -xzvf olca-ipc*
cd dist/working/olca-ipc-0.0.10/ && cp -r olca ../../blenderbim/libs/site/packages/
rm -rf dist/working
# Provides Brickschema fucntionality
# Provides Brickschema functionality
mkdir dist/working
cd dist/working && wget https://files.pythonhosted.org/packages/ca/37/8309da9a72407d2f5eb5489c197ac3cfe96ee3ccba1902d2a0ecc7843f92/brickschema-0.5.1.tar.gz
cd dist/working && tar -xzvf brickschema*
@@ -518,7 +638,7 @@ endif
cd dist/working/ && cp -r pkg_resources ../blenderbim/libs/site/packages/
rm -rf dist/working
cd dist/blenderbim && sed -i "s/999999/$(VERSION)/" __init__.py
cd dist/blenderbim && $(SED) "s/999999/$(VERSION)/" __init__.py
cd dist && zip -r blenderbim-$(VERSION)-$(PYVERSION)-$(PLATFORM).zip ./*
rm -rf dist/blenderbim
@@ -62,6 +62,7 @@ modules = {
"clash": None,
"lca": None,
"csv": None,
"tester": None,
"bimtester": None,
"diff": None,
"patch": None,
@@ -90,9 +91,12 @@ classes = [
operator.RemoveIfcFile,
operator.SelectDataDir,
operator.SelectIfcFile,
operator.ReloadSelectedIfcFile,
operator.SelectSchemaDir,
operator.SelectURIAttribute,
operator.EditBlenderCollection,
operator.BIM_OT_open_webbrowser,
operator.BIM_OT_show_description,
prop.StrProperty,
operator.BIM_OT_enum_property_search, # /!\ Register AFTER prop.StrProperty
prop.ObjProperty,
@@ -113,6 +117,7 @@ classes = [
ui.BIM_PT_project_info,
ui.BIM_PT_project_setup,
ui.BIM_PT_collaboration,
ui.BIM_PT_selection,
ui.BIM_PT_geometry,
ui.BIM_PT_services,
ui.BIM_PT_structural,
@@ -157,6 +162,8 @@ def register():
bpy.types.Camera.BIMMeshProperties = bpy.props.PointerProperty(type=prop.BIMMeshProperties)
bpy.types.PointLight.BIMMeshProperties = bpy.props.PointerProperty(type=prop.BIMMeshProperties)
bpy.types.SCENE_PT_unit.append(ui.ifc_units)
if hasattr(bpy.types, "UI_MT_button_context_menu"):
bpy.types.UI_MT_button_context_menu.append(ui.draw_custom_context_menu)
for mod in modules.values():
mod.register()
@@ -177,6 +184,8 @@ def unregister():
del bpy.types.Camera.BIMMeshProperties
del bpy.types.PointLight.BIMMeshProperties
bpy.types.SCENE_PT_unit.remove(ui.ifc_units)
if hasattr(bpy.types, "UI_MT_button_context_menu"):
bpy.types.UI_MT_button_context_menu.remove(ui.draw_custom_context_menu)
for mod in reversed(list(modules.values())):
mod.unregister()
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
@@ -1,13 +1,13 @@
ISO-10303-21;
HEADER;
FILE_DESCRIPTION(('ViewDefinition[DesignTransferView]'),'2;1');
FILE_NAME('/dev/null','2021-11-08T10:06:41+11:00',(),(),'IfcOpenShell v0.7.0-ab506098','IfcOpenShell v0.7.0-ab506098','Nobody');
FILE_NAME('/dev/null','2022-10-14T18:00:26+11:00',(),(),'IfcOpenShell v0.7.0-dc67287d','IfcOpenShell v0.7.0-dc67287d','Nobody');
FILE_SCHEMA(('IFC4'));
ENDSEC;
DATA;
#1=IFCPROJECT('1oJZZVd$b1PA6ggg_hh4cE',$,'BlenderBIM Demo',$,$,$,$,(#12,#16),#7);
#2=IFCPROJECTLIBRARY('3jWhYPJYL2eOMZ9UZerKqx',$,'BlenderBIM Demo Library',$,$,$,$,$,$);
#3=IFCRELDECLARES('1Wjv8iy5XBshHpEwChjCFa',$,$,$,#1,(#2));
#1=IFCPROJECT('0yOx2IKFL4HAbf$IjzE0Kp',$,'BlenderBIM Demo',$,$,$,$,(#12,#16),#7);
#2=IFCPROJECTLIBRARY('2jUXWJsnH2pxWl9Bl3Ljhd',$,'BlenderBIM Demo Library',$,$,$,$,$,$);
#3=IFCRELDECLARES('0Rn4fWoen7xB9hskGGEfEy',$,$,$,#1,(#2));
#4=IFCSIUNIT(*,.LENGTHUNIT.,$,.METRE.);
#5=IFCSIUNIT(*,.AREAUNIT.,$,.SQUARE_METRE.);
#6=IFCSIUNIT(*,.VOLUMEUNIT.,$,.CUBIC_METRE.);
@@ -24,82 +24,82 @@ DATA;
#17=IFCGEOMETRICREPRESENTATIONSUBCONTEXT('Body','Model',*,*,*,*,#12,$,.MODEL_VIEW.,$);
#18=IFCGEOMETRICREPRESENTATIONSUBCONTEXT('Annotation','Plan',*,*,*,*,#16,$,.PLAN_VIEW.,$);
#19=IFCMATERIAL('Unknown',$,$);
#20=IFCWALLTYPE('24Js5pn1PCeA_DXGWh67_f',$,'DEMO50',$,$,$,$,$,$,$);
#20=IFCWALLTYPE('0D2QFqmA18fvA6TUteIRXc',$,'WAL50',$,$,$,$,$,$,.NOTDEFINED.);
#21=IFCMATERIALLAYERSET((#23),$,$);
#22=IFCRELASSOCIATESMATERIAL('3gPuTuaVf1AAgeln9$alDb',$,$,$,(#20),#21);
#22=IFCRELASSOCIATESMATERIAL('0WbN7alqv8ix3w4MOdyl8F',$,$,$,(#20),#21);
#23=IFCMATERIALLAYER(#19,0.05,$,$,$,$,$);
#24=IFCRELDECLARES('3AJpFBgAf4OQzrklK7UbEn',$,$,$,#2,(#37,#41,#76,#47,#58,#91,#25,#225,#71,#159,#29,#62,#95,#20,#53,#86,#33,#66,#81));
#25=IFCWALLTYPE('1HCqnc9S51NhU$e0Xceg6o',$,'DEMO100',$,$,$,$,$,$,$);
#24=IFCRELDECLARES('13kodoVGr08Bn7WfGWndV_',$,$,$,#2,(#37,#41,#76,#47,#58,#91,#25,#225,#71,#159,#29,#62,#95,#20,#53,#86,#33,#66,#81));
#25=IFCWALLTYPE('0qRxb2OqjDEfFI9i7b6qAw',$,'WAL100',$,$,$,$,$,$,.NOTDEFINED.);
#26=IFCMATERIALLAYERSET((#28),$,$);
#27=IFCRELASSOCIATESMATERIAL('2Ikoi$G2PCXvk61V36wh3N',$,$,$,(#25),#26);
#27=IFCRELASSOCIATESMATERIAL('3BDm6HH553QhwCW7FPmjrR',$,$,$,(#25),#26);
#28=IFCMATERIALLAYER(#19,0.1,$,$,$,$,$);
#29=IFCWALLTYPE('3UPlZcHuTCi8CevFkzQjZ_',$,'DEMO200',$,$,$,$,$,$,$);
#29=IFCWALLTYPE('1KMENjmzj7cf_am9SYEaXW',$,'WAL200',$,$,$,$,$,$,.NOTDEFINED.);
#30=IFCMATERIALLAYERSET((#32),$,$);
#31=IFCRELASSOCIATESMATERIAL('3uAo69i4j4nwMM7VRW3TQn',$,$,$,(#29),#30);
#31=IFCRELASSOCIATESMATERIAL('1$36L06DrDYx1sZ9tBQ_WE',$,$,$,(#29),#30);
#32=IFCMATERIALLAYER(#19,0.2,$,$,$,$,$);
#33=IFCWALLTYPE('1y03E8PfTAJPit3967Y5Uf',$,'DEMO300',$,$,$,$,$,$,$);
#33=IFCWALLTYPE('2y5Y19etz3rhrt8P45fuGA',$,'WAL300',$,$,$,$,$,$,.NOTDEFINED.);
#34=IFCMATERIALLAYERSET((#36),$,$);
#35=IFCRELASSOCIATESMATERIAL('2pvy9lWJL62BoA9QC5KCOG',$,$,$,(#33),#34);
#35=IFCRELASSOCIATESMATERIAL('1WSONmheTD9xhiluqtfktr',$,$,$,(#33),#34);
#36=IFCMATERIALLAYER(#19,0.3,$,$,$,$,$);
#37=IFCCOVERINGTYPE('3ppA8IYrP2nffVvxie6ghZ',$,'DEMO10',$,$,$,$,$,$,$);
#37=IFCCOVERINGTYPE('1pAuiYopL5hxCATiIPV8QG',$,'COV10',$,$,$,$,$,$,.NOTDEFINED.);
#38=IFCMATERIALLAYERSET((#40),$,$);
#39=IFCRELASSOCIATESMATERIAL('2Lg3S9QRH4kPgHpYrtokt6',$,$,$,(#37),#38);
#39=IFCRELASSOCIATESMATERIAL('0ysRf97LT8fw28mDwt2vop',$,$,$,(#37),#38);
#40=IFCMATERIALLAYER(#19,0.01,$,$,$,$,$);
#41=IFCCOVERINGTYPE('3KuCqMbY16DRUygYOrLyYE',$,'DEMO20',$,$,(#45),$,$,$,$);
#41=IFCCOVERINGTYPE('00LbT8PQf3XwImYkj_i_$4',$,'COV20',$,$,(#45),$,$,$,.NOTDEFINED.);
#42=IFCMATERIALLAYERSET((#44),$,$);
#43=IFCRELASSOCIATESMATERIAL('27BA_9uQT6OfxSGgEbsKY4',$,$,$,(#41),#42);
#43=IFCRELASSOCIATESMATERIAL('0Kvr3quG19xw6$528t4NQi',$,$,$,(#41),#42);
#44=IFCMATERIALLAYER(#19,0.02,$,$,$,$,$);
#45=IFCPROPERTYSET('11$WHz$79EMw60BdaUh77w',$,'EPset_Parametric',$,(#46));
#45=IFCPROPERTYSET('1IZqsXecj8NQZcNAIAY48P',$,'EPset_Parametric',$,(#46));
#46=IFCPROPERTYSINGLEVALUE('LayerSetDirection',$,IFCLABEL('AXIS2'),$);
#47=IFCCOVERINGTYPE('2cYyQpe9f1vBW8C4D58QZS',$,'DEMO30',$,$,(#51),$,$,$,$);
#47=IFCCOVERINGTYPE('10YNsLMi9409CbEVFmDkJt',$,'COV30',$,$,(#51),$,$,$,.NOTDEFINED.);
#48=IFCMATERIALLAYERSET((#50),$,$);
#49=IFCRELASSOCIATESMATERIAL('1ir_2IiXX4Ax4VH5lcXGEu',$,$,$,(#47),#48);
#49=IFCRELASSOCIATESMATERIAL('2jZVWXllT9ugYrdBaIBAnt',$,$,$,(#47),#48);
#50=IFCMATERIALLAYER(#19,0.03,$,$,$,$,$);
#51=IFCPROPERTYSET('2IalIx1bj02AXv21tZC0Sz',$,'EPset_Parametric',$,(#52));
#51=IFCPROPERTYSET('2M2_M1eSXFYR8vFE96Ecy1',$,'EPset_Parametric',$,(#52));
#52=IFCPROPERTYSINGLEVALUE('LayerSetDirection',$,IFCLABEL('AXIS3'),$);
#53=IFCRAMPTYPE('23wMhiydD6yw5XpaFs6XCA',$,'DEMO200',$,$,$,$,$,$,$);
#53=IFCRAMPTYPE('09P6PVRiT44vIjup23Olfq',$,'RAM200',$,$,$,$,$,$,.NOTDEFINED.);
#54=IFCMATERIALLAYERSET((#56),$,$);
#55=IFCRELASSOCIATESMATERIAL('2SLXH7fU1D6OIOdV8Gq6T_',$,$,$,(#53),#54);
#55=IFCRELASSOCIATESMATERIAL('2Ghy1OMS1FLAOmdzrFp4rp',$,$,$,(#53),#54);
#56=IFCMATERIALLAYER(#19,0.2,$,$,$,$,$);
#57=IFCCIRCLEPROFILEDEF(.AREA.,$,$,0.3);
#58=IFCPILETYPE('3J5r90lrL3kv3b8uqOGsY1',$,'DEMO1',$,$,$,$,$,$,$);
#58=IFCPILETYPE('0X0OrCsUnDUPVeAu90N_K$',$,'P1',$,$,$,$,$,$,.NOTDEFINED.);
#59=IFCMATERIALPROFILESET($,$,(#61),$);
#60=IFCRELASSOCIATESMATERIAL('2vYxThgzHDSOHXKehbpw4S',$,$,$,(#58),#59);
#60=IFCRELASSOCIATESMATERIAL('1FiRvXL$nBkuT8B3FfiGT9',$,$,$,(#58),#59);
#61=IFCMATERIALPROFILE($,$,#19,#57,$,$);
#62=IFCSLABTYPE('0OWxf_nd97uvH$Sr4QbAVW',$,'DEMO150',$,$,$,$,$,$,$);
#62=IFCSLABTYPE('1helZ086v1o8JD3Ow1eWAy',$,'FLR150',$,$,$,$,$,$,.NOTDEFINED.);
#63=IFCMATERIALLAYERSET((#65),$,$);
#64=IFCRELASSOCIATESMATERIAL('3WSlLxuwH3Sfsul8DwR0LU',$,$,$,(#62),#63);
#64=IFCRELASSOCIATESMATERIAL('3BbM02xKH6h97dBaMJp$Rr',$,$,$,(#62),#63);
#65=IFCMATERIALLAYER(#19,0.2,$,$,$,$,$);
#66=IFCSLABTYPE('0wVKQLFFb2oAHccogmWWWy',$,'DEMO250',$,$,$,$,$,$,$);
#66=IFCSLABTYPE('1YAagoi$9D_hPiWtssbkcG',$,'FLR250',$,$,$,$,$,$,.NOTDEFINED.);
#67=IFCMATERIALLAYERSET((#69),$,$);
#68=IFCRELASSOCIATESMATERIAL('01R$pwjx14qOlDDfB34EBC',$,$,$,(#66),#67);
#68=IFCRELASSOCIATESMATERIAL('0m1oulHjT1u889pxJG$XG_',$,$,$,(#66),#67);
#69=IFCMATERIALLAYER(#19,0.3,$,$,$,$,$);
#70=IFCRECTANGLEPROFILEDEF(.AREA.,$,$,0.5,0.6);
#71=IFCCOLUMNTYPE('2OGUvw6Tj9deH3dTY4Hk3T',$,'DEMO1',$,$,$,$,$,$,$);
#71=IFCCOLUMNTYPE('1lzPzyyFfFYRgjqMHnKZLx',$,'C1',$,$,$,$,$,$,.NOTDEFINED.);
#72=IFCMATERIALPROFILESET($,$,(#74),$);
#73=IFCRELASSOCIATESMATERIAL('1f717ijc53DgmnoMRFBZ62',$,$,$,(#71),#72);
#73=IFCRELASSOCIATESMATERIAL('26fuP2VpfEnhlUsk0K1i4y',$,$,$,(#71),#72);
#74=IFCMATERIALPROFILE($,$,#19,#70,$,$);
#75=IFCCIRCLEHOLLOWPROFILEDEF(.AREA.,$,$,0.25,0.005);
#76=IFCCOLUMNTYPE('3_jg43QeDAWvPCl1D0Lw_q',$,'DEMO2',$,$,$,$,$,$,$);
#76=IFCCOLUMNTYPE('1qqu_K2Az3yvZSOapZmUrZ',$,'C2',$,$,$,$,$,$,.NOTDEFINED.);
#77=IFCMATERIALPROFILESET($,$,(#79),$);
#78=IFCRELASSOCIATESMATERIAL('3mX_vYbF92vxfOrjZFAuYk',$,$,$,(#76),#77);
#78=IFCRELASSOCIATESMATERIAL('2YQz9JCGXAogjkmN3RZeKI',$,$,$,(#76),#77);
#79=IFCMATERIALPROFILE($,$,#19,#75,$,$);
#80=IFCRECTANGLEHOLLOWPROFILEDEF(.AREA.,$,$,0.075,0.15,0.005,0.005,0.005);
#81=IFCCOLUMNTYPE('2ZJTQa2Db0MAv2N5bjUuXK',$,'DEMO3',$,$,$,$,$,$,$);
#81=IFCCOLUMNTYPE('1rnXRgQ3X6pfk5O9j4HCLm',$,'C3',$,$,$,$,$,$,.NOTDEFINED.);
#82=IFCMATERIALPROFILESET($,$,(#84),$);
#83=IFCRELASSOCIATESMATERIAL('0Yslq2hlz9Qe3PO16oqESz',$,$,$,(#81),#82);
#83=IFCRELASSOCIATESMATERIAL('2CQLZ1qlzA9uN2eRrpg99c',$,$,$,(#81),#82);
#84=IFCMATERIALPROFILE($,$,#19,#80,$,$);
#85=IFCISHAPEPROFILEDEF(.AREA.,'DEMO-I',$,0.1,0.2,0.005,0.01,0.005,$,$);
#86=IFCBEAMTYPE('2bUnl1uHnB$QSMF_fVajUw',$,'DEMO1',$,$,$,$,$,$,$);
#86=IFCBEAMTYPE('1xjrF860f2H9zb04Ui98Rt',$,'B1',$,$,$,$,$,$,.NOTDEFINED.);
#87=IFCMATERIALPROFILESET($,$,(#89),$);
#88=IFCRELASSOCIATESMATERIAL('08d9DxbyHAyRgRPc6W4Qsn',$,$,$,(#86),#87);
#88=IFCRELASSOCIATESMATERIAL('03i9c81690fx2ZTBBXcqkL',$,$,$,(#86),#87);
#89=IFCMATERIALPROFILE($,$,#19,#85,$,$);
#90=IFCCSHAPEPROFILEDEF(.AREA.,'DEMO-C',$,0.2,0.1,0.0015,0.03,0.005);
#91=IFCBEAMTYPE('0f1mNiwDTAiuSgrvotHh5$',$,'DEMO2',$,$,$,$,$,$,$);
#91=IFCBEAMTYPE('3mAc_7AGP06w4CzYOlYVL2',$,'B2',$,$,$,$,$,$,.NOTDEFINED.);
#92=IFCMATERIALPROFILESET($,$,(#94),$);
#93=IFCRELASSOCIATESMATERIAL('2cO0XhlFf8MxIg_eNDWX_X',$,$,$,(#91),#92);
#93=IFCRELASSOCIATESMATERIAL('2xvXgaSvL7KPR25fXz0fSE',$,$,$,(#91),#92);
#94=IFCMATERIALPROFILE($,$,#19,#90,$,$);
#95=IFCWINDOWTYPE('0ihTx2Iw1E2gRAq1N4pWVg',$,'DEMO1',$,$,$,(#136,#158),$,$,$,$,$,$);
#95=IFCWINDOWTYPE('1kxjrhA4P2dwwueZ0_nSuu',$,'WT01',$,$,$,(#136,#158),$,$,.NOTDEFINED.,.NOTDEFINED.,$,$);
#96=IFCINDEXEDPOLYGONALFACE((13,17,18,14));
#97=IFCINDEXEDPOLYGONALFACE((5,6,3,4));
#98=IFCINDEXEDPOLYGONALFACE((7,8,2,1));
@@ -126,14 +126,14 @@ DATA;
#119=IFCPOLYGONALFACESET(#118,$,(#96,#97,#98,#99,#100,#102,#103,#104,#105,#106,#107,#108,#109,#110,#111,#112),$);
#120=IFCPOLYGONALFACESET(#118,$,(#101,#113,#114,#115,#116,#117),$);
#121=IFCSHAPEREPRESENTATION(#17,'Body','Tessellation',(#119,#120));
#122=IFCSURFACESTYLE('Frame',.BOTH.,(#125));
#123=IFCCOLOURRGB($,0.0429765619337559,0.0429765619337559,0.0429765619337559);
#122=IFCSURFACESTYLE('Frame',.BOTH.,(#123));
#123=IFCSURFACESTYLERENDERING(#124,0.,#125,$,$,$,IFCNORMALISEDRATIOMEASURE(0.),IFCSPECULARROUGHNESS(0.4),.NOTDEFINED.);
#124=IFCCOLOURRGB($,0.0429765619337559,0.0429765619337559,0.0429765619337559);
#125=IFCSURFACESTYLERENDERING(#123,0.,#124,$,$,$,$,$,$);
#126=IFCSURFACESTYLE('Glass',.BOTH.,(#129));
#127=IFCCOLOURRGB($,0.800000011920929,1.,1.);
#125=IFCCOLOURRGB($,0.800000011920929,0.800000011920929,0.800000011920929);
#126=IFCSURFACESTYLE('Glass',.BOTH.,(#127));
#127=IFCSURFACESTYLERENDERING(#128,0.,#129,$,$,$,IFCNORMALISEDRATIOMEASURE(0.),IFCSPECULARROUGHNESS(0.5),.NOTDEFINED.);
#128=IFCCOLOURRGB($,0.800000011920929,1.,1.);
#129=IFCSURFACESTYLERENDERING(#127,0.799999997019768,#128,$,$,$,$,$,$);
#129=IFCCOLOURRGB($,0.800000011920929,0.800000011920929,0.800000011920929);
#130=IFCSTYLEDITEM(#119,(#122),'Frame');
#131=IFCSTYLEDITEM(#120,(#126),'Glass');
#132=IFCCARTESIANPOINT((0.,0.,0.));
@@ -163,7 +163,7 @@ DATA;
#156=IFCDIRECTION((0.,0.,1.));
#157=IFCAXIS2PLACEMENT3D(#154,#156,#155);
#158=IFCREPRESENTATIONMAP(#157,#153);
#159=IFCDOORTYPE('0HLh8Cp3f58hay4OhB7SHy',$,'DEMO1',$,$,$,(#204,#224),$,$,$,$,$,$);
#159=IFCDOORTYPE('0s370BWH51r8Xg03pJ5OBE',$,'DT01',$,$,$,(#204,#224),$,$,.NOTDEFINED.,.NOTDEFINED.,$,$);
#160=IFCINDEXEDPOLYGONALFACE((17,16,15,14));
#161=IFCINDEXEDPOLYGONALFACE((2,3,29,28));
#162=IFCINDEXEDPOLYGONALFACE((27,28,29,30,32,31));
@@ -194,14 +194,14 @@ DATA;
#187=IFCPOLYGONALFACESET(#186,$,(#161,#162,#163,#164,#165,#166,#169,#170,#171,#172,#173,#174,#175,#176,#179,#180,#182,#183,#184,#185),$);
#188=IFCPOLYGONALFACESET(#186,$,(#160,#167,#168,#177,#178,#181),$);
#189=IFCSHAPEREPRESENTATION(#17,'Body','Tessellation',(#187,#188));
#190=IFCSURFACESTYLE('Frame',.BOTH.,(#193));
#191=IFCCOLOURRGB($,0.0429765619337559,0.0429765619337559,0.0429765619337559);
#190=IFCSURFACESTYLE('Frame',.BOTH.,(#191));
#191=IFCSURFACESTYLERENDERING(#192,0.,#193,$,$,$,IFCNORMALISEDRATIOMEASURE(0.),IFCSPECULARROUGHNESS(0.4),.NOTDEFINED.);
#192=IFCCOLOURRGB($,0.0429765619337559,0.0429765619337559,0.0429765619337559);
#193=IFCSURFACESTYLERENDERING(#191,0.,#192,$,$,$,$,$,$);
#194=IFCSURFACESTYLE('Panel',.BOTH.,(#197));
#195=IFCCOLOURRGB($,0.184475064277649,0.184475019574165,0.184475019574165);
#193=IFCCOLOURRGB($,0.800000011920929,0.800000011920929,0.800000011920929);
#194=IFCSURFACESTYLE('Panel',.BOTH.,(#195));
#195=IFCSURFACESTYLERENDERING(#196,0.,#197,$,$,$,IFCNORMALISEDRATIOMEASURE(0.),IFCSPECULARROUGHNESS(0.5),.NOTDEFINED.);
#196=IFCCOLOURRGB($,0.184475064277649,0.184475019574165,0.184475019574165);
#197=IFCSURFACESTYLERENDERING(#195,0.,#196,$,$,$,$,$,$);
#197=IFCCOLOURRGB($,0.800000011920929,0.800000011920929,0.800000011920929);
#198=IFCSTYLEDITEM(#187,(#190),'Frame');
#199=IFCSTYLEDITEM(#188,(#194),'Panel');
#200=IFCCARTESIANPOINT((0.,0.,0.));
@@ -229,7 +229,7 @@ DATA;
#222=IFCDIRECTION((0.,0.,1.));
#223=IFCAXIS2PLACEMENT3D(#220,#222,#221);
#224=IFCREPRESENTATIONMAP(#223,#219);
#225=IFCFURNITURETYPE('1wgo6Pw4jDJO4F1GA9Qlmx',$,'BUNNY',$,$,$,(#946,#988),$,$,$,$);
#225=IFCFURNITURETYPE('227RrM$yD30RGzlTgbBH3c',$,'BUN01',$,$,$,(#946,#988),$,$,.NOTDEFINED.,.NOTDEFINED.);
#226=IFCINDEXEDPOLYGONALFACE((187,278,44));
#227=IFCINDEXEDPOLYGONALFACE((21,52,60));
#228=IFCINDEXEDPOLYGONALFACE((91,100,31));
File diff suppressed because one or more lines are too long
+2
View File
@@ -26,6 +26,7 @@ from blenderbim.bim.module.drawing.prop import RasterStyleProperty
from bpy.app.handlers import persistent
from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.module.owner.prop import get_user_person, get_user_organisation
from blenderbim.bim.module.model.data import AuthoringData
from ifcopenshell.api.material.data import Data as MaterialData
from ifcopenshell.api.type.data import Data as TypeData
@@ -301,3 +302,4 @@ def setDefaultProperties(scene):
drawing_style.name = "Blender Default"
drawing_style.render_type = "DEFAULT"
bpy.ops.bim.save_drawing_style(index="2")
AuthoringData.type_thumbnails = {}
+108 -13
View File
@@ -16,14 +16,17 @@
# You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
# from datetime import date
import bpy
import json
import math
import zipfile
import ifcopenshell
import ifcopenshell.util.attribute
from ifcopenshell.util.doc import get_attribute_doc, get_predefined_type_doc, get_property_doc
from mathutils import geometry
from mathutils import Vector
import blenderbim.tool as tool
from blenderbim.bim.ifc import IfcStore
@@ -40,12 +43,26 @@ def draw_attribute(attribute, layout, copy_operator=None):
return
if value_name == "enum_value":
prop_with_search(layout, attribute, "enum_value", text=attribute.name)
elif attribute.name in ["ScheduleDuration", "ActualDuration", "FreeFloat", "TotalFloat"]:
propis = bpy.context.scene.BIMWorkScheduleProperties
for item in propis.durations_attributes:
if item.name == attribute.name:
duration_props = item
layout.label(text=attribute.name)
layout.prop(duration_props, "years", text="Y")
layout.prop(duration_props, "months", text="M")
layout.prop(duration_props, "days", text="D")
layout.prop(duration_props, "hours", text="H")
layout.prop(duration_props, "minutes", text="Min")
layout.prop(duration_props, "seconds", text="S")
break
else:
layout.prop(
attribute,
value_name,
text=attribute.name,
)
if attribute.is_optional:
layout.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="")
if copy_operator:
@@ -77,25 +94,74 @@ def import_attribute(attribute, props, data, callback=None):
new.is_null = data[attribute.name()] is None
new.is_optional = attribute.optional()
new.data_type = data_type if isinstance(data_type, str) else ""
new.ifc_class = data["type"]
is_handled_by_callback = callback(attribute.name(), new, data) if callback else None
if is_handled_by_callback:
pass # Our job is done
elif is_handled_by_callback is False:
props.remove(len(props) - 1)
elif data_type == "string":
new.string_value = "" if new.is_null else data[attribute.name()]
new.string_value = "" if new.is_null else str(data[attribute.name()])
if attribute.type_of_attribute().declared_type().name() == "IfcURIReference":
new.is_uri = True
elif data_type == "boolean":
new.bool_value = False if new.is_null else data[attribute.name()]
new.bool_value = False if new.is_null else bool(data[attribute.name()])
elif data_type == "integer":
new.int_value = 0 if new.is_null else data[attribute.name()]
new.int_value = 0 if new.is_null else int(data[attribute.name()])
elif data_type == "float":
new.float_value = 0.0 if new.is_null else data[attribute.name()]
new.float_value = 0.0 if new.is_null else float(data[attribute.name()])
elif data_type == "enum":
new.enum_items = json.dumps(ifcopenshell.util.attribute.get_enum_items(attribute))
if data[attribute.name()]:
new.enum_value = data[attribute.name()]
enum_items = ifcopenshell.util.attribute.get_enum_items(attribute)
new.enum_items = json.dumps(enum_items)
add_attribute_enum_items_descriptions(new, enum_items)
if data[new.name]:
new.enum_value = data[new.name]
add_attribute_description(new, data)
add_attribute_min_max(new)
ATTRIBUTE_MIN_MAX_CONSTRAINTS = {"IfcMaterialLayer": {"Priority": {"value_min": 0, "value_max": 100}}}
def add_attribute_min_max(attribute_blender):
if attribute_blender.ifc_class in ATTRIBUTE_MIN_MAX_CONSTRAINTS:
constraints = ATTRIBUTE_MIN_MAX_CONSTRAINTS[attribute_blender.ifc_class].get(attribute_blender.name, {})
for constraint, value in constraints.items():
setattr(attribute_blender, constraint, value)
setattr(attribute_blender, constraint + "_constraint", True)
def add_attribute_enum_items_descriptions(attribute_blender, enum_items):
attribute_blender.enum_descriptions.clear()
if isinstance(enum_items, dict):
enum_items = enum_items.keys()
version = tool.Ifc.get_schema()
for enum_item in enum_items:
new_enum_description = attribute_blender.enum_descriptions.add()
try:
description = get_predefined_type_doc(version, attribute_blender.ifc_class, enum_item) or ""
except KeyError: # TODO this only supports predefined type enums. Add support for other types of enums ?
description = ""
new_enum_description.name = description
def add_attribute_description(attribute_blender, attribute_ifc=None):
if not attribute_blender.name:
return
version = tool.Ifc.get_schema()
description = ""
try:
description = get_attribute_doc(version, attribute_blender.ifc_class, attribute_blender.name)
except RuntimeError: # It's not an Entity Attribute. Let's try a Property Set attribute.
doc = get_property_doc(version, attribute_blender.ifc_class, attribute_blender.name)
if doc:
description = doc.get("description", "")
else: # It's a custom property set. Check if this attribute has a description
if attribute_ifc is not None:
description = getattr(attribute_ifc, "Description", "")
if description:
attribute_blender.description = description
def export_attributes(props, callback=None):
@@ -111,14 +177,18 @@ def export_attributes(props, callback=None):
def prop_with_search(layout, data, prop_name, **kwargs):
# kwargs are layout.prop arguments (text, icon, etc.)
row = layout.row(align=True)
# Magick courtesy of https://blender.stackexchange.com/a/203443/86891
row.context_pointer_set(name="data", data=data)
row.prop(data, prop_name, **kwargs)
op = row.operator("bim.enum_property_search", text="", icon="VIEWZOOM")
op.prop_name = prop_name
try:
if len(get_enum_items(data, prop_name)) > 10:
# Magick courtesy of https://blender.stackexchange.com/a/203443/86891
row.context_pointer_set(name="data", data=data)
op = row.operator("bim.enum_property_search", text="", icon="VIEWZOOM")
op.prop_name = prop_name
except TypeError: # Prop is not iterable
pass
def get_enum_items(data, prop_name, context):
def get_enum_items(data, prop_name, context=None):
# Retrieve items from a dynamic EnumProperty, which is otherwise not supported
# Or throws an error in the console when the items callback returns an empty list
# See https://blender.stackexchange.com/q/215781/86891
@@ -128,10 +198,35 @@ def get_enum_items(data, prop_name, context):
return
if not isinstance(items, (list, tuple)):
# items are retrieved through a callback, not a static list :
items = items(data, context)
items = items(data, context or bpy.context)
return items
def get_obj_ifc_definition_id(context, obj, obj_type):
if obj_type == "Object":
return bpy.data.objects.get(obj).BIMObjectProperties.ifc_definition_id
elif obj_type == "Material":
return bpy.data.materials.get(obj).BIMObjectProperties.ifc_definition_id
elif obj_type == "Task":
return context.scene.BIMTaskTreeProperties.tasks[
context.scene.BIMWorkScheduleProperties.active_task_index
].ifc_definition_id
elif obj_type == "Cost":
return context.scene.BIMCostProperties.cost_items[
context.scene.BIMCostProperties.active_cost_item_index
].ifc_definition_id
elif obj_type == "Resource":
return context.scene.BIMResourceTreeProperties.resources[
context.scene.BIMResourceProperties.active_resource_index
].ifc_definition_id
elif obj_type == "Profile":
return context.scene.BIMProfileProperties.profiles[
context.scene.BIMProfileProperties.active_profile_index
].ifc_definition_id
elif obj_type == "WorkSchedule":
return context.scene.BIMWorkScheduleProperties.active_work_schedule_id
# hack to close popup
# https://blender.stackexchange.com/a/202576/130742
def close_operator_panel(event):
+52 -6
View File
@@ -39,6 +39,8 @@ class IfcStore:
edited_objs = set()
pset_template_path = ""
pset_template_file = None
classification_path = ""
classification_file = None
library_path = ""
library_file = None
element_listeners = set()
@@ -48,7 +50,7 @@ class IfcStore:
last_transaction = ""
history = []
future = []
schema_identifiers = ["IFC4", "IFC2X3"]
schema_identifiers = ["IFC4", "IFC2X3", "IFC4X3"]
@staticmethod
def purge():
@@ -68,7 +70,7 @@ class IfcStore:
IfcStore.last_transaction = ""
IfcStore.history = []
IfcStore.future = []
IfcStore.schema_identifiers = ["IFC4", "IFC2X3"]
IfcStore.schema_identifiers = ["IFC4", "IFC2X3", "IFC4X3"]
@staticmethod
def get_file():
@@ -223,7 +225,11 @@ class IfcStore:
try:
value.name
except:
obj = bpy.data.objects.get(IfcStore.undo_redo_stack_object_names[key])
# TODO not so sure about this obj_name check
obj_name = IfcStore.undo_redo_stack_object_names.get(key, None)
if not obj_name:
continue
obj = bpy.data.objects.get(obj_name)
if not obj or not obj.BIMObjectProperties.ifc_definition_id:
continue
element = file.by_id(obj.BIMObjectProperties.ifc_definition_id)
@@ -232,7 +238,6 @@ class IfcStore:
data["guid"] = element.GlobalId
IfcStore.commit_link_element(data)
@staticmethod
def relink_all_objects():
if not IfcStore.get_file():
@@ -247,13 +252,19 @@ class IfcStore:
if not obj:
return
if obj.BIMObjectProperties.ifc_definition_id:
element = IfcStore.get_file().by_id(obj.BIMObjectProperties.ifc_definition_id)
try:
element = IfcStore.get_file().by_id(obj.BIMObjectProperties.ifc_definition_id)
except:
return
data = {"id": element.id(), "obj": obj.name}
if hasattr(element, "GlobalId"):
data["guid"] = element.GlobalId
IfcStore.commit_link_element(data)
if hasattr(obj, "BIMMaterialProperties") and obj.BIMMaterialProperties.ifc_style_id:
element = IfcStore.get_file().by_id(obj.BIMMaterialProperties.ifc_style_id)
try:
element = IfcStore.get_file().by_id(obj.BIMMaterialProperties.ifc_style_id)
except:
return
data = {"id": element.id(), "obj": obj.name}
IfcStore.commit_link_element(data)
@@ -276,6 +287,15 @@ class IfcStore:
@staticmethod
def link_element(element, obj):
existing_obj = IfcStore.id_map.get(element.id(), None)
if existing_obj == obj:
return
elif existing_obj:
try:
existing_obj.name
IfcStore.unlink_element(obj=existing_obj)
except:
pass
IfcStore.id_map[element.id()] = obj
if hasattr(element, "GlobalId"):
IfcStore.guid_map[element.GlobalId] = obj
@@ -322,6 +342,21 @@ class IfcStore:
# TODO Listeners are not re-registered. Does this cause nasty problems to debug later on?
# TODO We're handling id_map and guid_map, but what about edited_objs? This might cause big problems.
@staticmethod
def rollback_unlink_element(data):
if "id" not in data or "obj" not in data:
return
obj = bpy.data.objects.get(data["obj"])
IfcStore.id_map[data["id"]] = obj
if data["guid"]:
IfcStore.guid_map[data["guid"]] = obj
@staticmethod
def commit_unlink_element(data):
del IfcStore.id_map[data["id"]]
if data["guid"]:
del IfcStore.guid_map[data["guid"]]
@staticmethod
def unlink_element(element=None, obj=None):
if element is None:
@@ -357,6 +392,17 @@ class IfcStore:
elif obj:
obj.BIMObjectProperties.ifc_definition_id = 0
if IfcStore.history:
data = {}
if element:
data["id"] = element.id()
data["guid"] = getattr(element, "GlobalId", None)
if obj:
data["obj"] = obj.name
IfcStore.history[-1]["operations"].append(
{"rollback": IfcStore.rollback_unlink_element, "commit": IfcStore.commit_unlink_element, "data": data}
)
@staticmethod
def execute_ifc_operator(operator, context):
is_top_level_operator = not bool(IfcStore.current_transaction)
+112 -53
View File
@@ -112,7 +112,7 @@ class MaterialCreator:
def parse_representation(self, representation):
has_parsed = False
representation_items = self.resolve_all_representation_items(representation)
representation_items = self.resolve_all_stylable_representation_items(representation)
for item in representation_items:
if self.parse_representation_item(item):
has_parsed = True
@@ -153,11 +153,19 @@ class MaterialCreator:
]
self.mesh.polygons.foreach_set("material_index", material_index)
def resolve_all_representation_items(self, representation):
def resolve_all_stylable_representation_items(self, representation):
items = []
for item in representation.Items:
if item.is_a("IfcMappedItem"):
items.extend(item.MappingSource.MappedRepresentation.Items)
if item.is_a("IfcBooleanResult"):
operand = item.FirstOperand
while True:
items.append(operand)
if operand.is_a("IfcBooleanResult"):
operand = operand.FirstOperand
else:
break
items.append(item)
return items
@@ -177,11 +185,11 @@ class IfcImporter:
self.settings_2d.set(self.settings_2d.INCLUDE_CURVES, True)
self.settings_2d.set(self.settings.STRICT_TOLERANCE, True)
self.project = None
self.has_existing_project = False
self.collections = {}
self.elements = set()
self.type_collection = None
self.type_products = {}
self.openings = {}
self.meshes = {}
self.mesh_shapes = {}
self.time = 0
@@ -189,6 +197,7 @@ class IfcImporter:
self.added_data = {}
self.native_elements = set()
self.native_data = {}
self.progress = 0
self.material_creator = MaterialCreator(ifc_import_settings, self)
@@ -206,7 +215,6 @@ class IfcImporter:
def execute(self):
bpy.context.window_manager.progress_begin(0, 100)
self.progress = 0
self.profile_code("Starting import process")
self.load_file()
self.profile_code("Loading file")
@@ -224,8 +232,6 @@ class IfcImporter:
self.profile_code("Process context filter")
self.create_collections()
self.profile_code("Create collections")
self.create_openings_collection()
self.profile_code("Create opening collection")
self.create_materials()
self.profile_code("Create materials")
self.create_styles()
@@ -322,11 +328,23 @@ class IfcImporter:
self.elements = self.file.by_type("IfcElement")
self.annotations = set(self.file.by_type("IfcAnnotation"))
self.elements = [e for e in self.elements if not e.is_a("IfcFeatureElement")]
if self.ifc_import_settings.is_coordinating:
self.elements = [e for e in self.elements if e.Representation and not e.is_a("IfcFeatureElement")]
self.elements = [e for e in self.elements if e.Representation]
self.elements = set(self.elements[offset:offset_limit])
self.element_types = set([ifcopenshell.util.element.get_type(e) for e in self.elements])
if self.ifc_import_settings.has_filter or offset or offset_limit < len(self.elements):
self.element_types = set([ifcopenshell.util.element.get_type(e) for e in self.elements])
else:
if self.file.schema in ("IFC2X3", "IFC4"):
self.element_types = set(
self.file.by_type("IfcElementType")
+ self.file.by_type("IfcDoorStyle")
+ self.file.by_type("IfcWindowStyle")
)
else:
self.element_types = set(self.file.by_type("IfcElementType"))
if self.ifc_import_settings.has_filter and self.ifc_import_settings.should_filter_spatial_elements:
self.spatial_elements = self.get_spatial_elements_filtered_by_elements(self.elements)
@@ -396,6 +414,12 @@ class IfcImporter:
items = representation["raw"].Items or [] # Be forgiving of invalid IFCs because Revit :(
if len(items) == 1 and items[0].is_a("IfcSweptDiskSolid"):
return True
elif len(items) and ( # See #2508 why we accommodate for invalid IFCs here
items[0].is_a("IfcSweptDiskSolid")
and len({i.is_a() for i in items}) == 1
and len({i.Radius for i in items}) == 1
):
return True
return False
def is_native_faceted_brep(self, representations):
@@ -504,14 +528,21 @@ class IfcImporter:
def does_element_likely_have_geometry_far_away(self, element):
for representation in element.Representation.Representations:
for subelement in self.file.traverse(representation):
if subelement.is_a("IfcCartesianPointList3D"):
for point in subelement.CoordList:
if len(point) == 3 and self.is_point_far_away(point, is_meters=False):
items = []
for item in representation.Items:
if item.is_a("IfcMappedItem"):
items.extend(item.MappingSource.MappedRepresentation.Items)
else:
items.append(item)
for item in items:
for subelement in self.file.traverse(item):
if subelement.is_a("IfcCartesianPointList3D"):
for point in subelement.CoordList:
if len(point) == 3 and self.is_point_far_away(point, is_meters=False):
return True
if subelement.is_a("IfcCartesianPoint"):
if len(subelement.Coordinates) == 3 and self.is_point_far_away(subelement, is_meters=False):
return True
if subelement.is_a("IfcCartesianPoint"):
if len(subelement.Coordinates) == 3 and self.is_point_far_away(subelement, is_meters=False):
return True
def apply_blender_offset_to_matrix_world(self, obj, matrix):
props = bpy.context.scene.BIMGeoreferenceProperties
@@ -552,8 +583,9 @@ class IfcImporter:
if grid.Representation:
shape = ifcopenshell.geom.create_shape(self.settings_2d, grid)
grid_obj = self.create_product(grid, shape)
grid_obj.lock_location = (True, True, True)
grid_obj.lock_rotation = (True, True, True)
if bpy.context.preferences.addons["blenderbim"].preferences.lock_grids_on_import:
grid_obj.lock_location = (True, True, True)
grid_obj.lock_rotation = (True, True, True)
collection = bpy.data.collections.new(self.get_name(grid))
u_axes = bpy.data.collections.new("UAxes")
collection.children.link(u_axes)
@@ -571,8 +603,9 @@ class IfcImporter:
shape = ifcopenshell.geom.create_shape(self.settings_2d, axis.AxisCurve)
mesh = self.create_mesh(axis, shape)
obj = bpy.data.objects.new(f"IfcGridAxis/{axis.AxisTag}", mesh)
obj.lock_location = (True, True, True)
obj.lock_rotation = (True, True, True)
if bpy.context.preferences.addons["blenderbim"].preferences.lock_grids_on_import:
obj.lock_location = (True, True, True)
obj.lock_rotation = (True, True, True)
self.link_element(axis, obj)
self.set_matrix_world(obj, grid_obj.matrix_world)
grid_collection.objects.link(obj)
@@ -712,12 +745,12 @@ class IfcImporter:
self.incrementally_merge_objects()
shape = iterator.get()
if shape:
product = self.file.by_id(shape.guid)
product = self.file.by_id(shape.id)
if self.body_contexts:
self.create_product(product, shape)
results.add(product)
else:
if shape.context not in ["Body", "Facetation"] and IfcStore.get_element(shape.guid):
if shape.context not in ["Body", "Facetation"] and IfcStore.get_element(shape.id):
# We only load a single context, and we prioritise the Body context. See #1290.
pass
else:
@@ -867,7 +900,7 @@ class IfcImporter:
checkpoint = time.time()
shape = iterator.get()
if shape:
product = self.file.by_id(shape.guid)
product = self.file.by_id(shape.id)
self.create_product(product, shape)
results.add(product)
if not iterator.next():
@@ -879,6 +912,11 @@ class IfcImporter:
if element is None:
return
if self.has_existing_project:
obj = tool.Ifc.get_object(element)
if obj:
return obj
self.ifc_import_settings.logger.info("Creating object %s", element)
if mesh:
@@ -886,7 +924,7 @@ class IfcImporter:
elif element.is_a("IfcAnnotation") and element.ObjectType == "DRAWING":
mesh = self.create_camera(element, shape)
self.link_mesh(shape, mesh)
elif element.is_a("IfcAnnotation") and self.is_curve_annotation(element):
elif element.is_a("IfcAnnotation") and self.is_curve_annotation(element) and shape:
mesh = self.create_curve(element, shape)
self.link_mesh(shape, mesh)
elif shape:
@@ -916,10 +954,6 @@ class IfcImporter:
elif hasattr(element, "ObjectPlacement"):
self.set_matrix_world(obj, self.apply_blender_offset_to_matrix_world(obj, self.get_element_matrix(element)))
self.add_opening_relation(element, obj)
if element.is_a("IfcOpeningElement"):
obj.display_type = "WIRE"
return obj
def get_representation_item_material_name(self, item):
@@ -1103,6 +1137,7 @@ class IfcImporter:
polyline.points[-1].co = representation["matrix"] @ mathutils.Vector(v2)
curve.bevel_depth = self.unit_scale * item.Radius
curve.use_fill_caps = True
return curve
def create_native_annotation(self, element, mesh_name):
@@ -1234,7 +1269,6 @@ class IfcImporter:
# Occurs when reloading a project
pass
project_collection = bpy.context.view_layer.layer_collection.children[self.project["blender"].name]
project_collection.children[self.opening_collection.name].hide_viewport = True
project_collection.children[self.type_collection.name].hide_viewport = True
def clean_mesh(self):
@@ -1257,11 +1291,6 @@ class IfcImporter:
bpy.ops.object.editmode_toggle(context_override)
IfcStore.edited_objs.clear()
def add_opening_relation(self, element, obj):
if not element.is_a("IfcOpeningElement"):
return
self.openings[element.GlobalId] = obj
def load_file(self):
self.ifc_import_settings.logger.info("loading file %s", self.ifc_import_settings.input_file)
if not bpy.context.scene.BIMProperties.ifc_file:
@@ -1301,7 +1330,13 @@ class IfcImporter:
)
def create_project(self):
self.project = {"ifc": self.file.by_type("IfcProject")[0]}
project = self.file.by_type("IfcProject")[0]
self.project = {"ifc": project}
obj = tool.Ifc.get_object(project)
if obj:
self.project["blender"] = obj.users_collection[0]
self.has_existing_project = True
return
self.project["blender"] = bpy.data.collections.new(
"{}/{}".format(self.project["ifc"].is_a(), self.project["ifc"].Name)
)
@@ -1322,6 +1357,13 @@ class IfcImporter:
def create_spatial_decomposition_collections(self):
for rel_aggregate in self.project["ifc"].IsDecomposedBy or []:
self.create_spatial_decomposition_collection(self.project["blender"], rel_aggregate.RelatedObjects)
# Invalid IFCs may have orphaned spatial structure elements.
orphaned_spaces = [e for e in self.spatial_elements if e.GlobalId not in self.collections]
while orphaned_spaces:
self.create_spatial_decomposition_collection(self.project["blender"], orphaned_spaces)
orphaned_spaces = [e for e in self.spatial_elements if e.GlobalId not in self.collections]
self.create_views_collection()
self.create_type_collection()
@@ -1354,10 +1396,17 @@ class IfcImporter:
for element in related_objects:
if element not in self.spatial_elements:
continue
global_id = element.GlobalId
collection = bpy.data.collections.new(self.get_name(element))
self.collections[global_id] = collection
parent.children.link(collection)
is_existing = False
if self.has_existing_project:
obj = tool.Ifc.get_object(element)
if obj:
is_existing = True
collection = obj.users_collection[0]
self.collections[element.GlobalId] = collection
if not is_existing:
collection = bpy.data.collections.new(self.get_name(element))
self.collections[element.GlobalId] = collection
parent.children.link(collection)
if element.IsDecomposedBy:
for rel_aggregate in element.IsDecomposedBy:
self.create_spatial_decomposition_collection(collection, rel_aggregate.RelatedObjects)
@@ -1368,7 +1417,11 @@ class IfcImporter:
rel_aggregates += [e.Decomposes[0] for e in self.elements if e.Decomposes]
rel_aggregates = set(rel_aggregates)
else:
rel_aggregates = [a for a in self.file.by_type("IfcRelAggregates") if a.RelatingObject.is_a("IfcElement")]
rel_aggregates = [
a
for a in self.file.by_type("IfcRelAggregates")
if a.RelatingObject.is_a("IfcElement") or a.RelatingObject.is_a("IfcElementType")
]
if len(rel_aggregates) > 10000:
# More than 10,000 collections makes Blender unhappy
@@ -1391,10 +1444,10 @@ class IfcImporter:
parent = ifcopenshell.util.element.get_container(aggregate["element"])
if parent:
self.collections[parent.GlobalId].children.link(aggregate["collection"])
def create_openings_collection(self):
self.opening_collection = bpy.data.collections.new("IfcOpeningElements")
self.project["blender"].children.link(self.opening_collection)
continue
if aggregate["element"].is_a("IfcElementType"):
self.type_collection.children.link(aggregate["collection"])
continue
def create_materials(self):
for material in self.file.by_type("IfcMaterial"):
@@ -1472,13 +1525,21 @@ class IfcImporter:
if surface_style.ReflectanceMethod in ["PHYSICAL", "NOTDEFINED"]:
blender_material.use_nodes = True
bsdf = blender_material.node_tree.nodes["Principled BSDF"]
if surface_style.DiffuseColour and surface_style.DiffuseColour.is_a("IfcColourRgb"):
bsdf.inputs["Base Color"].default_value = (
surface_style.DiffuseColour.Red,
surface_style.DiffuseColour.Green,
surface_style.DiffuseColour.Blue,
1,
)
if surface_style.DiffuseColour:
if surface_style.DiffuseColour.is_a("IfcColourRgb"):
bsdf.inputs["Base Color"].default_value = (
surface_style.DiffuseColour.Red,
surface_style.DiffuseColour.Green,
surface_style.DiffuseColour.Blue,
1,
)
elif surface_style.DiffuseColour.is_a("IfcNormalisedRatioMeasure"):
bsdf.inputs["Base Color"].default_value = (
surface_style.SurfaceColour.Red * surface_style.DiffuseColour.wrappedValue,
surface_style.SurfaceColour.Green * surface_style.DiffuseColour.wrappedValue,
surface_style.SurfaceColour.Blue * surface_style.DiffuseColour.wrappedValue,
1,
)
if surface_style.SpecularColour and surface_style.SpecularColour.is_a("IfcNormalisedRatioMeasure"):
bsdf.inputs["Metallic"].default_value = surface_style.SpecularColour.wrappedValue
if surface_style.SpecularHighlight and surface_style.SpecularHighlight.is_a("IfcSpecularRoughness"):
@@ -1640,8 +1701,6 @@ class IfcImporter:
return self.collections[element.GlobalId].objects.link(obj)
elif element.is_a("IfcTypeObject"):
return self.type_collection.objects.link(obj)
elif element.is_a("IfcOpeningElement"):
return self.opening_collection.objects.link(obj)
elif element.is_a("IfcStructuralMember"):
return self.structural_member_collection.objects.link(obj)
elif element.is_a("IfcStructuralConnection"):
@@ -1952,7 +2011,7 @@ class IfcImporter:
def set_default_context(self):
for subcontext in self.file.by_type("IfcGeometricRepresentationSubContext"):
if subcontext.ContextIdentifier == "Body":
bpy.context.scene.BIMProperties.contexts = str(subcontext.id())
bpy.context.scene.BIMRootProperties.contexts = str(subcontext.id())
break
def link_element(self, element, obj):
@@ -28,3 +28,7 @@ class BcfStore:
if not BcfStore.bcfxml:
BcfStore.bcfxml = bcf.v2.bcfxml.BcfXml()
return BcfStore.bcfxml
@classmethod
def set(cls, bcfxml):
cls.bcfxml = bcfxml
@@ -20,7 +20,6 @@ import os
import bpy
import bcf
import bcf.bcfxml
import bcf.v2.data
import numpy as np
import ifcopenshell
import ifcopenshell.util.unit
@@ -38,8 +37,8 @@ class NewBcfProject(bpy.types.Operator):
def execute(self, context):
context.scene.BCFProperties.is_loaded = False
bcfxml = bcfstore.BcfStore.get_bcfxml()
bcfxml.new_project()
bcfxml = bcf.v2.bcfxml.BcfXml.create_new("New Project")
bcfstore.BcfStore.set(bcfxml)
bpy.ops.bim.load_bcf_project()
context.scene.BCFProperties.is_loaded = True
return {"FINISHED"}
@@ -97,34 +96,34 @@ class LoadBcfTopic(bpy.types.Operator):
new = context.scene.BCFProperties.topics[self.topic_index]
data_map = {
"name": topic.guid,
"title": topic.title,
"type": topic.topic_type,
"status": topic.topic_status,
"priority": topic.priority,
"stage": topic.stage,
"creation_date": topic.creation_date,
"creation_author": topic.creation_author,
"modified_date": topic.modified_date,
"modified_author": topic.modified_author,
"assigned_to": topic.assigned_to,
"due_date": topic.due_date,
"description": topic.description,
"title": topic.topic.title,
"type": topic.topic.topic_type,
"status": topic.topic.topic_status,
"priority": topic.topic.priority,
"stage": topic.topic.stage,
"creation_date": topic.topic.creation_date,
"creation_author": topic.topic.creation_author,
"modified_date": topic.topic.modified_date,
"modified_author": topic.topic.modified_author,
"assigned_to": topic.topic.assigned_to,
"due_date": topic.topic.due_date,
"description": topic.topic.description,
}
for key, value in data_map.items():
if value is not None:
setattr(new, key, str(value))
new.reference_links.clear()
for reference_link in topic.reference_links:
for reference_link in topic.topic.reference_link:
new_reference_link = new.reference_links.add()
new_reference_link.name = reference_link
new.labels.clear()
for label in topic.labels:
for label in topic.topic.labels:
new_label = new.labels.add()
new_label.name = label
if topic.bim_snippet:
if topic.topic.bim_snippet:
data_map = {
"type": topic.bim_snippet.snippet_type,
"is_external": topic.bim_snippet.is_external,
@@ -136,7 +135,7 @@ class LoadBcfTopic(bpy.types.Operator):
setattr(new.bim_snippet, key, value)
new.document_references.clear()
for doc in topic.document_references:
for doc in topic.topic.document_reference:
new_document_references = new.document_references.add()
data_map = {
"reference": doc.referenced_document,
@@ -149,7 +148,7 @@ class LoadBcfTopic(bpy.types.Operator):
setattr(new_document_references, key, value)
new.related_topics.clear()
for related_topic in topic.related_topics:
for related_topic in topic.topic.related_topic:
new_related_topic = new.related_topics.add()
new_related_topic.name = related_topic.guid
@@ -276,7 +275,7 @@ class AddBcfTopic(bpy.types.Operator):
def execute(self, context):
bcfxml = bcfstore.BcfStore.get_bcfxml()
bcfxml.add_topic()
bcfxml.add_topic("New Topic", "", context.scene.BCFProperties.author)
bpy.ops.bim.load_bcf_topics()
return {"FINISHED"}
@@ -62,7 +62,7 @@ def refreshScenarios(self, context):
class BimTesterProperties(PropertyGroup):
feature: StringProperty(default="", name="Feature / IDS", update=refreshScenarios)
feature: StringProperty(default="", name="Feature", update=refreshScenarios)
steps: StringProperty(default="", name="Custom Steps")
ifc_file: StringProperty(default="", name="IFC File")
audit_ifc_class: EnumProperty(items=get_ifc_classes, name="Audit Class")
@@ -49,7 +49,7 @@ class Loader:
if not boundary.ConnectionGeometry:
return None
surface = boundary.ConnectionGeometry.SurfaceOnRelatingElement
# workaround for unvalid geometry provided by Revit. See https://github.com/IfcOpenShell/IfcOpenShell/issues/635#issuecomment-770366838
# workaround for invalid geometry provided by Revit. See https://github.com/IfcOpenShell/IfcOpenShell/issues/635#issuecomment-770366838
if surface.is_a("IfcCurveBoundedPlane") and not getattr(surface, "InnerBoundaries", None):
surface.InnerBoundaries = ()
shape = ifcopenshell.geom.create_shape(self.settings, surface)
@@ -285,6 +285,7 @@ class UpdateBoundaryGeometry(bpy.types.Operator):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
tool.Boundary.move_origin_to_space_origin(context.active_object)
settings = tool.Boundary.get_assign_connection_geometry_settings(context.active_object)
ifcopenshell.api.run("boundary.assign_connection_geometry", tool.Ifc.get(), **settings)
return {"FINISHED"}
@@ -67,7 +67,6 @@ class BIM_PT_Boundary(Panel):
def draw(self, context):
props = context.active_object.BIMObjectProperties
ifc_file = tool.Ifc.get()
element = ifc_file.by_id(props.ifc_definition_id)
boundary = ifc_file.by_id(props.ifc_definition_id)
self.bprops = context.active_object.bim_boundary_properties
if self.bprops.is_editing:
@@ -101,7 +100,7 @@ class BIM_PT_Boundary(Panel):
entity = getattr(boundary, ifc_attribute)
if entity:
row.label(text=f"{entity.is_a()}/{entity.Name}")
op = row.operator("bim.select_global_id", text="", icon="TRACKER")
op = row.operator("bim.select_global_id", text="", icon="OBJECT_DATA")
op.global_id = entity.GlobalId
else:
row.label(text="")
@@ -51,7 +51,6 @@ class BIM_PT_brickschema(Panel):
row = self.layout.row(align=True)
prop_with_search(row, self.props, "namespace", text="")
prop_with_search(row, self.props, "brick_equipment_class", text="")
row.prop(self.props, "brick_equipment_class", text="")
row.operator("bim.add_brick", text="", icon="ADD")
row = self.layout.row(align=True)
@@ -17,14 +17,20 @@
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import bpy
from . import operator, workspace
from . import operator, prop, workspace
classes = (
operator.AlignViewToProfile,
operator.AddIfcArcIndexFillet,
operator.AddIfcCircle,
operator.AddRectangle,
operator.CadArcFrom2Points,
operator.CadArcFrom3Points,
operator.CadFillet,
operator.CadMitre,
operator.CadOffset,
operator.CadTrimExtend,
prop.BIMCadProperties,
workspace.CadHotkey,
)
@@ -32,8 +38,10 @@ classes = (
def register():
if not bpy.app.background:
bpy.utils.register_tool(workspace.CadTool, after={"builtin.transform"}, separator=True, group=True)
bpy.types.Scene.BIMCadProperties = bpy.props.PointerProperty(type=prop.BIMCadProperties)
def unregister():
if not bpy.app.background:
bpy.utils.unregister_tool(workspace.CadTool)
del bpy.types.Scene.BIMCadProperties
@@ -22,6 +22,8 @@ import bmesh
import mathutils
import bpy_extras
import blenderbim.tool as tool
from mathutils import Vector, Matrix
from math import pi, radians, sin, cos, sqrt
messages = {
"SHARED_VERTEX": "Shared Vertex, no intersection possible",
@@ -40,7 +42,6 @@ class CadTrimExtend(bpy.types.Operator):
return bool(obj) and obj.type == "MESH"
def cancel_message(self, msg):
print(msg)
self.report({"WARNING"}, msg)
return {"CANCELLED"}
@@ -69,10 +70,10 @@ class CadTrimExtend(bpy.types.Operator):
else:
return self.cancel_message("select two edges!")
bmesh.ops.remove_doubles(bm, verts=list(set(edges[0].verts) | set(edges[1].verts)), dist=1e-5)
bm.verts.index_update()
bm.edges.index_update()
bmesh.update_edit_mesh(me, loop_triangles=True)
return {"FINISHED"}
@@ -86,7 +87,6 @@ class CadMitre(bpy.types.Operator):
return bool(obj) and obj.type == "MESH"
def cancel_message(self, msg):
print(msg)
self.report({"WARNING"}, msg)
return {"CANCELLED"}
@@ -115,10 +115,10 @@ class CadMitre(bpy.types.Operator):
else:
return self.cancel_message("select two edges!")
bmesh.ops.remove_doubles(bm, verts=list(set(edges[0].verts) | set(edges[1].verts)), dist=1e-5)
bm.verts.index_update()
bm.edges.index_update()
bmesh.update_edit_mesh(me, loop_triangles=True)
return {"FINISHED"}
@@ -204,9 +204,9 @@ class CadFillet(bpy.types.Operator):
chamfer_edge = [e for e in bm.edges if shared_vert in e.verts and v2 in e.verts][0]
bm.edges.remove(chamfer_edge)
bm.verts.index_update()
bm.edges.index_update()
bmesh.update_edit_mesh(mesh)
mesh.update()
bm.free()
return {"FINISHED"}
@@ -255,7 +255,7 @@ class CadArcFrom2Points(bpy.types.Operator):
v = selected_verts[0]
bm.verts.remove(selected_verts[1])
axis = region_3d.view_rotation @ mathutils.Vector((0, 0, 1))
axis = region_3d.view_rotation @ Vector((0, 0, 1))
bmesh.ops.spin(
bm,
geom=[v],
@@ -297,49 +297,517 @@ class CadArcFrom3Points(bpy.types.Operator):
mesh = obj.data
bm = bmesh.from_edit_mesh(mesh)
selected_verts = [v for v in bm.verts if v.select]
if len(selected_verts) != 3:
arc = [v for v in bm.verts if v.select]
if len(arc) != 3:
return {"CANCELLED"}
pts = [v.co for v in selected_verts]
center = tool.Cad.get_center_of_arc(pts, obj)
if not center:
elif len([e for e in bm.edges if e.select]) != 2:
return {"CANCELLED"}
bpy.context.scene.cursor.location = center
if self.only_recalculate_center:
return {"FINISHED"}
sorted_arc = [None, None, None]
for v1 in arc:
connections = 0
for link_edge in v1.link_edges:
v2 = link_edge.other_vert(v1)
if v2 in arc:
connections += 1
if connections == 2: # Midpoint
sorted_arc[1] = v1
else:
sorted_arc[2 if sorted_arc[2] is None else 0] = v1
center = obj.matrix_world.inverted() @ center
points = [tuple(v.co) for v in sorted_arc]
verts, _ = tool.Cad.create_arc_segments(points, num_verts=(self.resolution * 4) + 1, make_edges=False)
def get_distance_to_other_points(vert):
other_verts = [v for v in selected_verts if v != vert]
total_vector = mathutils.Vector((0, 0, 0))
for other_vert in other_verts:
total_vector += other_vert.co - vert.co
return total_vector.length
bm.verts.index_update()
bm.edges.index_update()
# For three selected points that represent a start, end, and point on an
# arc, the start and end verts can be identified by being furthest away
# from the other 2 points.
arc_end_verts = sorted(selected_verts, key=get_distance_to_other_points)[-2:]
normal = mathutils.geometry.normal(pts)
index_offset = len(bm.verts)
new_verts = [bm.verts.new(v) for v in verts]
new_edges = [bm.edges.new((new_verts[i], new_verts[i + 1])) for i in range(len(new_verts) - 1)]
dir1 = (arc_end_verts[0].co - center).normalized()
dir2 = (arc_end_verts[1].co - center).normalized()
bm.verts.remove(sorted_arc[1])
bmesh.ops.remove_doubles(bm, verts=new_verts + [sorted_arc[0], sorted_arc[2]], dist=1e-5)
# Let's get the matrix that represents the coordinate system of the arc.
# This matrix allows us to get 2D vectors for calculating the signed arc angle.
z = normal
x = (arc_end_verts[0].co - center).normalized()
y = z.cross(x)
arc_matrix = mathutils.Matrix([x, y, z]).transposed().to_4x4()
dir1 = ((arc_matrix.inverted() @ arc_end_verts[0].co) - (arc_matrix.inverted() @ center)).normalized()
dir2 = ((arc_matrix.inverted() @ arc_end_verts[1].co) - (arc_matrix.inverted() @ center)).normalized()
angle = dir1.xy.angle_signed(dir2.xy)
v = arc_end_verts[0]
bmesh.ops.spin(bm, geom=[v], axis=normal, cent=center, steps=self.resolution * 4, angle=-angle)
bmesh.update_edit_mesh(mesh)
mesh.update()
return {"FINISHED"}
class CadOffset(bpy.types.Operator):
bl_idname = "bim.cad_offset"
bl_label = "CAD Offset"
bl_options = {"REGISTER", "UNDO"}
distance: bpy.props.FloatProperty(name="Distance", default=0.1)
@classmethod
def poll(self, context):
return context.mode == "EDIT_MESH"
def draw(self, context):
layout = self.layout
for prop in self.__class__.__annotations__.keys():
layout.prop(self, prop)
def cancel_message(self, msg):
self.report({"WARNING"}, msg)
return {"CANCELLED"}
def execute(self, context):
# This is not the same as Mesh Tools or Sverchok. Mesh Tools is based on
# a 3D "rail" which allows fancy 3D edge offsets, but will get different
# results to traditional CAD, which expects offsets to occur on a
# defined 2D workplane. This CAD offset does a pure 2D operation, so it
# always looks correct from the desired "2D workplane". This also
# reduces the settings down to one parameter instead of the huge config
# dialog that Mesh Tools has. Sverchok is 2D based, but has a weird side
# effect of converting an unclosed loop into a closed loop which is also
# not what users expect.
bpy.ops.object.mode_set(mode="OBJECT")
bpy.ops.object.mode_set(mode="EDIT")
obj = context.active_object
mw = obj.matrix_world
bm = bmesh.from_edit_mesh(obj.data)
bm.verts.ensure_lookup_table()
bm.edges.ensure_lookup_table()
edges = [e for e in bm.edges if e.select and not e.hide]
if len(edges) < 1:
return self.cancel_message("NO_EDGES")
verts = set()
[verts.update(e.verts) for e in edges]
# Use the viewport angle to determine the offset direction
for area in bpy.context.screen.areas:
if area.type == "VIEW_3D":
# Don't ask me, I don't know.
# view_matrix = area.spaces.active.region_3d.view_matrix
x = area.spaces.active.region_3d.view_rotation @ Vector((1, 0, 0))
y = area.spaces.active.region_3d.view_rotation @ Vector((0, 1, 0))
z = area.spaces.active.region_3d.view_rotation @ Vector((0, 0, 1))
wp = Matrix([x, y, z, Vector((0, 0, 0))]).to_4x4().transposed()
break
rotation = Matrix.Rotation(pi / 2, 2, "Z")
rotation_i = Matrix.Rotation(-pi / 2, 2, "Z")
# Create loops from edges
loop_edges = set(edges)
loops = []
while loop_edges:
edge = loop_edges.pop()
loop = [edge]
has_found_connected_edge = True
while has_found_connected_edge:
has_found_connected_edge = False
for edge in loop_edges.copy():
edge_verts = set(edge.verts)
if edge_verts & set(loop[0].verts):
loop.insert(0, edge)
loop_edges.remove(edge)
has_found_connected_edge = True
elif edge_verts & set(loop[-1].verts):
loop.append(edge)
loop_edges.remove(edge)
has_found_connected_edge = True
loops.append(loop)
for loop in loops:
all_verts = {v.index for e in loop for v in e.verts}
possible_v1s = []
is_closed = True
for edge in loop:
# If we have an open loop, start at either end instead
v = edge.verts[0]
if len(v.link_edges) == 1:
possible_v1s.append(v)
is_closed = False
else:
for link_edge in v.link_edges:
if link_edge.other_vert(v).index not in all_verts:
possible_v1s.append(v)
is_closed = False
break
v = edge.verts[1]
if len(v.link_edges) == 1:
possible_v1s.append(v)
is_closed = False
else:
for link_edge in v.link_edges:
if link_edge.other_vert(v).index not in all_verts:
possible_v1s.append(v)
is_closed = False
break
# Always start at the same vertex, so that when the operator is
# refreshed with new parameters the axes don't randomly get flipped.
if is_closed:
bm.verts.ensure_lookup_table()
bm.edges.ensure_lookup_table()
start = bm.verts[min(all_verts)]
else:
start = possible_v1s[0] if possible_v1s[0].index < possible_v1s[1].index else possible_v1s[1]
v1 = start
new_verts = []
processed_verts = set()
# [v0] --> v1 --> v2
while v1:
if v1.index in processed_verts:
break
link_verts = []
if len(v1.link_edges) == 1:
v = v1.link_edges[0].other_vert(v1)
link_verts.append(v) if v.index in all_verts else None
else:
v = v1.link_edges[0].other_vert(v1)
link_verts.append(v) if v.index in all_verts else None
v = v1.link_edges[1].other_vert(v1)
link_verts.append(v) if v.index in all_verts else None
if len(link_verts) == 1:
v2 = link_verts[0]
v0 = None if v2.index not in processed_verts else v2
v2 = None if v2.index in processed_verts else v2
else:
v2 = link_verts[0]
v0 = link_verts[1]
if v2.index in processed_verts:
if is_closed and v0.index in processed_verts:
v3 = start
v0 = v0 if v2 == v3 else v2
v2 = v3
else:
v0, v2 = v2, v0
normals = []
v1co = (wp.inverted() @ mw @ v1.co).to_2d()
if v2:
v2co = (wp.inverted() @ mw @ v2.co).to_2d()
direction = (v2co - v1co).normalized()
local_direction = (rotation @ direction).normalized()
normals.append(local_direction)
if v0:
v0co = (wp.inverted() @ mw @ v0.co).to_2d()
direction = (v0co - v1co).normalized()
local_direction = (rotation_i @ direction).normalized()
normals.append(local_direction)
if len(normals) == 2:
# https://stackoverflow.com/a/54042831/9627415
new_normal = (normals[0].lerp(normals[1], 0.5)).normalized()
offset_length = self.distance / sqrt((1 + normals[0].dot(normals[1])) / 2)
offset = mw.inverted().to_quaternion() @ (wp.to_quaternion() @ (new_normal * offset_length).to_3d())
new_vert = v1.co + offset
new_verts.append(bm.verts.new(new_vert))
else:
normal = (normals[0] * self.distance).to_3d()
offset = mw.inverted().to_quaternion() @ (wp.to_quaternion() @ normal)
new_vert = v1.co + offset
new_verts.append(bm.verts.new(new_vert))
processed_verts.add(v1.index)
if v2 in processed_verts:
break
v1 = v2
[bm.edges.new((new_verts[i], new_verts[i + 1])) for i in range(len(new_verts) - 1)]
if is_closed:
bm.edges.new((new_verts[len(new_verts) - 1], new_verts[0]))
bm.verts.index_update()
bm.edges.index_update()
bmesh.update_edit_mesh(obj.data)
return {"FINISHED"}
class AddIfcCircle(bpy.types.Operator):
bl_idname = "bim.add_ifccircle"
bl_label = "Add IfcCircle"
radius: bpy.props.FloatProperty(name="Radius", default=0.1)
@classmethod
def poll(cls, context):
obj = context.active_object
return bool(obj) and obj.type == "MESH"
def execute(self, context):
if self.has_selected_existing_circle(context):
self.change_radius(context)
else:
self.create_circle(context)
return {"FINISHED"}
def has_selected_existing_circle(self, context):
obj = context.active_object
bm = bmesh.from_edit_mesh(obj.data)
verts = [v for v in bm.verts if v.select and not v.hide]
if len(verts) != 2:
return False
groups = []
for i, group in enumerate(obj.vertex_groups):
if "IFCCIRCLE" in group.name:
groups.append(i)
bm.verts.layers.deform.verify()
deform_layer = bm.verts.layers.deform.active
for group in groups:
if group in verts[0][deform_layer] and group in verts[1][deform_layer]:
return True
def change_radius(self, context):
obj = context.active_object
bm = bmesh.from_edit_mesh(obj.data)
verts = [v for v in bm.verts if v.select and not v.hide]
center = verts[0].co.lerp(verts[1].co, 0.5)
verts[0].co = center + ((verts[0].co - center).normalized() * self.radius)
verts[1].co = center + ((verts[1].co - center).normalized() * self.radius)
bm.verts.index_update()
bm.edges.index_update()
bmesh.update_edit_mesh(obj.data)
def create_circle(self, context):
obj = context.active_object
bpy.ops.object.mode_set(mode="OBJECT")
# The last group may be the result of a prior run of this operator.
# I tried looping through all groups but Blender group indices seem to behave unpredictably.
if len(obj.vertex_groups):
last_group = obj.vertex_groups[-1]
verts_in_group = [v for v in obj.data.vertices if last_group.index in [vg.group for vg in v.groups]]
if "IFCCIRCLE" in last_group.name and len(verts_in_group) != 2:
obj.vertex_groups.remove(last_group)
group = obj.vertex_groups.new(name="IFCCIRCLE")
bpy.ops.object.mode_set(mode="EDIT")
bm = bmesh.from_edit_mesh(obj.data)
bm.verts.layers.deform.verify()
deform_layer = bm.verts.layers.deform.active
center = obj.matrix_world.inverted() @ context.scene.cursor.location
v1 = bm.verts.new((center[0], center[1] + self.radius, 0.0))
v2 = bm.verts.new((center[0], center[1] - self.radius, 0.0))
bm.verts.index_update()
bm.edges.new((v1, v2))
for vert in [v1, v2]:
vert[deform_layer][group.index] = 1
bm.verts.index_update()
bm.edges.index_update()
bmesh.update_edit_mesh(obj.data)
class AddIfcArcIndexFillet(bpy.types.Operator):
bl_idname = "bim.add_ifcarcindex_fillet"
bl_label = "Add Arc Index Fillet"
bl_options = {"REGISTER", "UNDO"}
radius: bpy.props.FloatProperty(name="Radius", default=0.1)
@classmethod
def poll(cls, context):
obj = context.active_object
return obj and obj.type == "MESH" and context.mode == "EDIT_MESH"
def draw(self, context):
layout = self.layout
for prop in self.__class__.__annotations__.keys():
layout.prop(self, prop)
def execute(self, context):
if self.has_selected_existing_arc(context):
self.change_radius(context)
else:
self.create_arc(context)
return {"FINISHED"}
def has_selected_existing_arc(self, context):
obj = context.active_object
bm = bmesh.from_edit_mesh(obj.data)
verts = [v for v in bm.verts if v.select and not v.hide]
if len(verts) != 3:
return False
groups = []
for i, group in enumerate(obj.vertex_groups):
if "IFCARCINDEX" in group.name:
groups.append(i)
bm.verts.layers.deform.verify()
deform_layer = bm.verts.layers.deform.active
for group in groups:
try:
if group in verts[0][deform_layer] and group in verts[1][deform_layer]:
return True
except:
pass # Potentially fail if the vert has been removed in the previous operation
def change_radius(self, context):
obj = context.active_object
bm = bmesh.from_edit_mesh(obj.data)
edges = [e for e in bm.edges if e.select and not e.hide]
mid = list(set(edges[0].verts) & set(edges[1].verts))[0]
v1 = edges[0].other_vert(mid)
v2 = edges[1].other_vert(mid)
center = tool.Cad.get_center_of_arc([v1.co, mid.co, v2.co])
if len(v1.link_edges) != 2 or len(v2.link_edges) != 2:
return
v3 = v1.link_edges[1].other_vert(v1) if mid in v1.link_edges[0].verts else v1.link_edges[0].other_vert(v1)
v4 = v2.link_edges[1].other_vert(v2) if mid in v2.link_edges[0].verts else v2.link_edges[0].other_vert(v2)
dir1 = (v3.co - v1.co).normalized()
dir2 = (v4.co - v2.co).normalized()
intersect = mathutils.geometry.intersect_line_line(v3.co, v1.co, v4.co, v2.co)[0]
edge_angle = dir1.angle(dir2)
slide_distance = self.radius / math.tan(edge_angle / 2)
v1.co = intersect + (dir1 * slide_distance)
v2.co = intersect + (dir2 * slide_distance)
normal = mathutils.geometry.normal([v1.co, intersect, v2.co])
center = mathutils.geometry.intersect_line_line(
v1.co, v1.co + normal.cross(dir1), v2.co, v2.co + normal.cross(dir2)
)[0]
mid.co = center + ((v1.co.lerp(v2.co, 0.5) - center).normalized() * self.radius)
bm.verts.index_update()
bm.edges.index_update()
bmesh.update_edit_mesh(obj.data)
def create_arc(self, context):
obj = bpy.context.active_object
bpy.ops.object.mode_set(mode="OBJECT")
# The last group may be the result of a prior run of this operator.
# I tried looping through all groups but Blender group indices seem to behave unpredictably.
if len(obj.vertex_groups):
last_group = obj.vertex_groups[-1]
verts_in_group = [v for v in obj.data.vertices if last_group.index in [vg.group for vg in v.groups]]
if "IFCARCINDEX" in last_group.name and len(verts_in_group) != 3:
obj.vertex_groups.remove(last_group)
group = obj.vertex_groups.new(name="IFCARCINDEX")
bpy.ops.object.mode_set(mode="EDIT")
bm = bmesh.from_edit_mesh(obj.data)
bm.verts.layers.deform.verify()
deform_layer = bm.verts.layers.deform.active
selected_edges = [e for e in bm.edges if e.select]
if len(selected_edges) != 2:
return {"CANCELLED"}
# Assume the user has selected two edges sharing a vert, but merge verts
# just in case the vertex is coincident but not shared.
all_verts = list(set(selected_edges[0].verts) | set(selected_edges[1].verts))
bmesh.ops.remove_doubles(bm, verts=all_verts, dist=1e-4)
# Calculate the distance to slide each edge to make space for the fillet arc
shared_vert = list(set(selected_edges[0].verts) & set(selected_edges[1].verts))[0]
v1 = selected_edges[0].other_vert(shared_vert)
v2 = selected_edges[1].other_vert(shared_vert)
dir1 = (v1.co - shared_vert.co).normalized()
dir2 = (v2.co - shared_vert.co).normalized()
edge_angle = dir1.angle(dir2)
slide_distance = self.radius / math.tan(edge_angle / 2)
angle = math.pi - edge_angle
fillet_v1co = shared_vert.co + (dir1 * slide_distance)
fillet_v2co = shared_vert.co + (dir2 * slide_distance)
normal = mathutils.geometry.normal([v1.co, shared_vert.co, v2.co])
center = mathutils.geometry.intersect_line_line(
fillet_v1co, fillet_v1co + normal.cross(dir1), fillet_v2co, fillet_v2co + normal.cross(dir2)
)[0]
fillet_v1 = bm.verts.new(fillet_v1co)
fillet_v2 = bm.verts.new(fillet_v2co)
midpointco = center + ((fillet_v1co.lerp(fillet_v2.co, 0.5) - center).normalized() * self.radius)
midpoint = bm.verts.new(midpointco)
bm.edges.new((v1, fillet_v1))
bm.edges.new((v2, fillet_v2))
bm.edges.new((fillet_v1, midpoint))
bm.edges.new((fillet_v2, midpoint))
bmesh.ops.delete(bm, geom=[shared_vert], context="VERTS")
for vert in [fillet_v1, midpoint, fillet_v2]:
vert[deform_layer][group.index] = 1
bm.verts.index_update()
bm.edges.index_update()
bmesh.update_edit_mesh(obj.data)
class AlignViewToProfile(bpy.types.Operator):
bl_idname = "bim.align_view_to_profile"
bl_label = "Align View To Profile"
# __ ___
# .'. -- . '.
# /U) __ (O|
# /.' ()() '.\._
# .',/;,_.--._.;;) . '--..__
# / ,///|.__.|.\\\ \ '. '.''---..___
# /'._ '' || || '' _'\ : \ ' . '.
# / || || '., ) ) : \
# :'-.__ _ || || _ __.' _\_ .' ' ' ,)
# ( ' |' ( __= ___..-._ ( (.\\
# ('\ .___ ___. /'.___= \.\.\
# \\\-..____________..-'///
@classmethod
def poll(cls, context):
obj = context.active_object
return bool(obj) and obj.type == "MESH"
def execute(self, context):
bpy.ops.object.mode_set(mode="OBJECT")
bpy.ops.view3d.view_axis(type="TOP", align_active=True)
region_3d = bpy.context.area.spaces.active.region_3d
region_3d.view_perspective = "ORTHO"
bpy.ops.object.mode_set(mode="EDIT")
return {"FINISHED"}
class AddRectangle(bpy.types.Operator):
bl_idname = "bim.add_rectangle"
bl_label = "Add Rectangle"
x: bpy.props.FloatProperty(name="X", default=0.1)
y: bpy.props.FloatProperty(name="Y", default=0.1)
@classmethod
def poll(cls, context):
obj = context.active_object
return bool(obj) and obj.type == "MESH"
def execute(self, context):
obj = context.active_object
bm = bmesh.from_edit_mesh(obj.data)
cursor = obj.matrix_world.inverted() @ context.scene.cursor.location
new_verts = [
bm.verts.new((cursor[0], cursor[1], 0.0)),
bm.verts.new((cursor[0] + self.x, cursor[1], 0.0)),
bm.verts.new((cursor[0] + self.x, cursor[1] + self.y, 0.0)),
bm.verts.new((cursor[0], cursor[1] + self.y, 0.0)),
]
[bm.edges.new((new_verts[i], new_verts[i + 1])) for i in range(len(new_verts) - 1)]
bm.edges.new((new_verts[len(new_verts) - 1], new_verts[0]))
bm.verts.index_update()
bm.edges.index_update()
bmesh.update_edit_mesh(obj.data)
return {"FINISHED"}
@@ -0,0 +1,30 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2022 Dion Moult <dion@thinkmoult.com>
#
# This file is part of BlenderBIM Add-on.
#
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# BlenderBIM Add-on is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import bpy
from blenderbim.bim.module.model.data import AuthoringData
from blenderbim.bim.module.model.root import ConstrTypeEntityNotFound
from bpy.types import PropertyGroup
class BIMCadProperties(PropertyGroup):
resolution: bpy.props.IntProperty(name="Arc Resolution", min=1, default=1)
radius: bpy.props.FloatProperty(name="Radius", default=0.1)
distance: bpy.props.FloatProperty(name="Distance", default=0.1)
x: bpy.props.FloatProperty(name="X", default=0.2)
y: bpy.props.FloatProperty(name="Y", default=0.1)
@@ -18,9 +18,9 @@
import os
import bpy
import blenderbim.tool as tool
import blenderbim.bim.module.type.prop as type_prop
from bpy.types import WorkSpaceTool
from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.module.model.data import AuthoringData
@@ -35,39 +35,100 @@ class CadTool(WorkSpaceTool):
bl_widget = None
# https://docs.blender.org/api/current/bpy.types.KeyMapItems.html
bl_keymap = (
("bim.cad_hotkey", {"type": "C", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_C")]}),
("bim.cad_hotkey", {"type": "E", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_E")]}),
("bim.cad_hotkey", {"type": "F", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_F")]}),
("bim.cad_hotkey", {"type": "O", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_O")]}),
("bim.cad_hotkey", {"type": "Q", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_Q")]}),
("bim.cad_hotkey", {"type": "R", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_R")]}),
("bim.cad_hotkey", {"type": "T", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_T")]}),
("bim.cad_fillet", {"type": "F", "value": "PRESS", "shift": True}, {"properties": []}),
# Enable Mesh Tools add-on to get this amazing tool
("mesh.offset_edges", {"type": "O", "value": "PRESS", "shift": True}, {"properties": []}),
("bim.cad_arc_from_2_points", {"type": "C", "value": "PRESS", "shift": True}, {"properties": []}),
("bim.cad_arc_from_3_points", {"type": "V", "value": "PRESS", "shift": True}, {"properties": []}),
("bim.cad_hotkey", {"type": "V", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_V")]}),
("bim.cad_hotkey", {"type": "X", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_X")]}),
)
def draw_settings(context, layout, tool):
row = layout.row(align=True)
row.label(text="", icon="EVENT_SHIFT")
row.label(text="Extend", icon="EVENT_E")
obj = context.active_object
if obj and obj.data and hasattr(obj.data, "BIMMeshProperties") and obj.data.BIMMeshProperties.is_profile:
row = layout.row(align=True)
row.label(text="", icon="EVENT_SHIFT")
row.label(text="", icon="EVENT_Q")
if obj.BIMObjectProperties.ifc_definition_id:
row.operator("bim.edit_extrusion_profile", text="Save Profile")
row.operator("bim.align_view_to_profile", text="", icon="AXIS_FRONT")
row.operator("bim.disable_editing_extrusion_profile", text="", icon="CANCEL")
else:
row.operator("bim.edit_arbitrary_profile", text="Save Profile")
row.operator("bim.align_view_to_profile", text="", icon="AXIS_FRONT")
row.operator("bim.disable_editing_arbitrary_profile", text="", icon="CANCEL")
row = layout.row(align=True)
row.label(text="", icon="EVENT_SHIFT")
row.label(text="Mitre", icon="EVENT_T")
row = layout.row(align=True)
row.label(text="", icon="EVENT_SHIFT")
row.label(text="", icon="EVENT_E")
row.operator("bim.cad_hotkey", text="Extend").hotkey = "S_E"
row = layout.row(align=True)
row.label(text="", icon="EVENT_SHIFT")
row.label(text="Fillet", icon="EVENT_F")
row = layout.row(align=True)
row.label(text="", icon="EVENT_SHIFT")
row.label(text="", icon="EVENT_T")
row.operator("bim.cad_hotkey", text="Mitre").hotkey = "S_T"
row = layout.row(align=True)
row.label(text="", icon="EVENT_SHIFT")
row.label(text="Offset", icon="EVENT_O")
row = layout.row(align=True)
row.label(text="", icon="EVENT_SHIFT")
row.label(text="", icon="EVENT_F")
row.operator("bim.cad_hotkey", text="Fillet").hotkey = "S_F"
row = layout.row(align=True)
row.label(text="", icon="EVENT_SHIFT")
row.label(text="2-Point Arc", icon="EVENT_C")
row = layout.row(align=True)
row.label(text="", icon="EVENT_SHIFT")
row.label(text="", icon="EVENT_O")
row.operator("bim.cad_hotkey", text="Offset").hotkey = "S_O"
row = layout.row(align=True)
row.label(text="", icon="EVENT_SHIFT")
row.label(text="3-Point Arc", icon="EVENT_V")
row = layout.row(align=True)
row.label(text="", icon="EVENT_SHIFT")
row.label(text="", icon="EVENT_R")
row.operator("bim.add_rectangle", text="Rectangle")
row = layout.row(align=True)
row.label(text="", icon="EVENT_SHIFT")
row.label(text="", icon="EVENT_C")
row.operator("bim.add_ifccircle", text="Circle")
row = layout.row(align=True)
row.label(text="", icon="EVENT_SHIFT")
row.label(text="", icon="EVENT_V")
row.operator("bim.set_arc_index", text="3-Point Arc")
row = layout.row(align=True)
row.label(text="", icon="EVENT_SHIFT")
row.label(text="", icon="EVENT_X")
row.operator("bim.reset_vertex", text="Reset Vertex")
else:
row = layout.row(align=True)
row.label(text="", icon="EVENT_SHIFT")
row.label(text="", icon="EVENT_E")
row.operator("bim.cad_hotkey", text="Extend").hotkey = "S_E"
row = layout.row(align=True)
row.label(text="", icon="EVENT_SHIFT")
row.label(text="", icon="EVENT_T")
row.operator("bim.cad_hotkey", text="Mitre").hotkey = "S_T"
row = layout.row(align=True)
row.label(text="", icon="EVENT_SHIFT")
row.label(text="", icon="EVENT_F")
row.operator("bim.cad_hotkey", text="Fillet").hotkey = "S_F"
row = layout.row(align=True)
row.label(text="", icon="EVENT_SHIFT")
row.label(text="", icon="EVENT_O")
row.operator("bim.cad_hotkey", text="Offset").hotkey = "S_O"
row = layout.row(align=True)
row.label(text="", icon="EVENT_SHIFT")
row.label(text="2-Point Arc", icon="EVENT_C")
row = layout.row(align=True)
row.label(text="", icon="EVENT_SHIFT")
row.label(text="", icon="EVENT_V")
row.operator("bim.cad_hotkey", text="3-Point Arc").hotkey = "S_V"
class CadHotkey(bpy.types.Operator):
@@ -77,17 +138,76 @@ class CadHotkey(bpy.types.Operator):
hotkey: bpy.props.StringProperty()
def execute(self, context):
self.props = context.scene.BIMModelProperties
self.has_ifc_class = True
try:
self.has_ifc_class = bool(self.props.ifc_class)
except:
pass
self.props = context.scene.BIMCadProperties
getattr(self, f"hotkey_{self.hotkey}")()
return {"FINISHED"}
def draw(self, context):
props = context.scene.BIMCadProperties
if self.hotkey == "S_C":
if self.is_profile():
row = self.layout.row()
row.prop(props, "radius")
elif self.hotkey == "S_F":
if not self.is_profile():
row = self.layout.row()
row.prop(props, "resolution")
row = self.layout.row()
row.prop(props, "radius")
elif self.hotkey == "S_O":
row = self.layout.row()
row.prop(props, "distance")
elif self.hotkey == "S_R":
row = self.layout.row()
row.prop(props, "x")
row = self.layout.row()
row.prop(props, "y")
elif self.hotkey == "S_V":
if not self.is_profile():
row = self.layout.row()
row.prop(props, "resolution")
def hotkey_S_C(self):
if self.is_profile():
bpy.ops.bim.add_ifccircle(radius=self.props.radius)
else:
bpy.ops.bim.cad_arc_from_2_points()
def hotkey_S_E(self):
bpy.ops.bim.cad_trim_extend()
def hotkey_S_F(self):
if self.is_profile():
bpy.ops.bim.add_ifcarcindex_fillet(radius=self.props.radius)
else:
bpy.ops.bim.cad_fillet(resolution=self.props.resolution, radius=self.props.radius)
def hotkey_S_O(self):
bpy.ops.bim.cad_offset(distance=self.props.distance)
def hotkey_S_Q(self):
if tool.Ifc.get_entity(bpy.context.active_object):
bpy.ops.bim.edit_extrusion_profile()
else:
bpy.ops.bim.edit_arbitrary_profile()
def hotkey_S_R(self):
if self.is_profile():
bpy.ops.bim.add_rectangle(x=self.props.x, y=self.props.y)
def hotkey_S_T(self):
bpy.ops.bim.cad_mitre()
def hotkey_S_V(self):
if self.is_profile():
bpy.ops.bim.set_arc_index()
else:
bpy.ops.bim.cad_arc_from_3_points(resolution=self.props.resolution)
def hotkey_S_X(self):
if self.is_profile():
bpy.ops.bim.reset_vertex()
def is_profile(self):
obj = bpy.context.active_object
return obj and obj.data and hasattr(obj.data, "BIMMeshProperties") and obj.data.BIMMeshProperties.is_profile
@@ -20,23 +20,26 @@ import bpy
from . import ui, prop, operator
classes = (
operator.LoadClassificationLibrary,
operator.AddClassification,
operator.RemoveClassification,
operator.EnableEditingClassification,
operator.DisableEditingClassification,
operator.EditClassification,
operator.RemoveClassificationReference,
operator.EnableEditingClassificationReference,
operator.DisableEditingClassificationReference,
operator.EditClassificationReference,
operator.AddClassificationReference,
operator.ChangeClassificationLevel,
operator.DisableEditingClassification,
operator.DisableEditingClassificationReference,
operator.DisableEditingClassificationReferences,
operator.EditClassification,
operator.EditClassificationReference,
operator.EnableEditingClassification,
operator.EnableEditingClassificationReference,
operator.LoadClassificationLibrary,
operator.RemoveClassification,
operator.RemoveClassificationReference,
prop.ClassificationReference,
prop.BIMClassificationProperties,
prop.BIMClassificationReferenceProperties,
ui.BIM_PT_classifications,
ui.BIM_PT_classification_references,
ui.BIM_PT_material_classifications,
ui.BIM_PT_cost_classifications,
ui.BIM_UL_classifications,
)
@@ -0,0 +1,143 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2022 Dion Moult <dion@thinkmoult.com>
#
# This file is part of BlenderBIM Add-on.
#
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# BlenderBIM Add-on is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import bpy
import ifcopenshell
import ifcopenshell.util.date
import ifcopenshell.util.classification
import blenderbim.tool as tool
from blenderbim.bim.ifc import IfcStore
def refresh():
ClassificationsData.is_loaded = False
ClassificationReferencesData.is_loaded = False
MaterialClassificationsData.is_loaded = False
CostClassificationsData.is_loaded = False
class ClassificationsData:
data = {}
is_loaded = False
@classmethod
def load(cls):
cls.is_loaded = True
cls.data["has_classification_file"] = cls.has_classification_file()
cls.data["classifications"] = cls.classifications()
cls.data["available_classifications"] = cls.available_classifications()
@classmethod
def has_classification_file(cls):
return bool(IfcStore.classification_file)
@classmethod
def classifications(cls):
results = []
for element in tool.Ifc.get().by_type("IfcClassification"):
data = element.get_info()
if tool.Ifc.get().schema == "IFC2X3" and element.EditionDate:
data["EditionDate"] = ifcopenshell.util.date.ifc2datetime(data["EditionDate"])
results.append(data)
return results
@classmethod
def available_classifications(cls):
if not IfcStore.classification_file:
return []
return [(str(e.id()), e.Name, "") for e in IfcStore.classification_file.by_type("IfcClassification")]
class ReferencesData:
@classmethod
def is_available_classification_added(cls):
if not IfcStore.classification_file or not IfcStore.classification_file.by_type("IfcClassification"):
return False
props = bpy.context.scene.BIMClassificationProperties
name = IfcStore.classification_file.by_id(int(props.available_classifications)).Name
return name in [e.Name for e in tool.Ifc.get().by_type("IfcClassification")]
class ClassificationReferencesData(ReferencesData):
data = {}
is_loaded = False
@classmethod
def load(cls):
cls.is_loaded = True
cls.data["references"] = cls.references()
cls.data["is_available_classification_added"] = cls.is_available_classification_added()
@classmethod
def references(cls):
results = []
element = tool.Ifc.get_entity(bpy.context.active_object)
if element:
for reference in ifcopenshell.util.classification.get_references(element):
data = reference.get_info()
del data["ReferencedSource"]
results.append(data)
return results
class MaterialClassificationsData(ReferencesData):
data = {}
is_loaded = False
@classmethod
def load(cls):
cls.is_loaded = True
cls.data["references"] = cls.references()
cls.data["is_available_classification_added"] = cls.is_available_classification_added()
@classmethod
def references(cls):
results = []
element = tool.Ifc.get_entity(bpy.context.active_object.active_material)
if element:
for reference in ifcopenshell.util.classification.get_references(element):
data = reference.get_info()
del data["ReferencedSource"]
results.append(data)
return results
class CostClassificationsData(ReferencesData):
data = {}
is_loaded = False
@classmethod
def load(cls):
cls.is_loaded = True
cls.data["references"] = cls.references()
cls.data["is_available_classification_added"] = cls.is_available_classification_added()
@classmethod
def references(cls):
results = []
element = tool.Ifc.get().by_id(
bpy.context.scene.BIMCostProperties.cost_items[
bpy.context.scene.BIMCostProperties.active_cost_item_index
].ifc_definition_id
)
if element:
for reference in ifcopenshell.util.classification.get_references(element):
data = reference.get_info()
del data["ReferencedSource"]
results.append(data)
return results
@@ -1,5 +1,5 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com>
# Copyright (C) 2020-2022 Dion Moult <dion@thinkmoult.com>
#
# This file is part of BlenderBIM Add-on.
#
@@ -18,46 +18,40 @@
import bpy
import json
import ifcopenshell
import ifcopenshell.api
import blenderbim.tool as tool
import blenderbim.bim.helper
from blenderbim.bim.ifc import IfcStore
from ifcopenshell.api.classification.data import Data
from blenderbim.bim.module.classification.prop import getClassifications, getReferences
class LoadClassificationLibrary(bpy.types.Operator):
class LoadClassificationLibrary(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.load_classification_library"
bl_label = "Load Classification Library"
filename_ext = ".ifc"
filter_glob: bpy.props.StringProperty(default="*.ifc;*.ifczip;*.ifcxml", options={"HIDDEN"})
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
def execute(self, context):
Data.load_library(self.filepath)
getClassifications(self, context)
return {"FINISHED"}
def invoke(self, context, event):
context.window_manager.fileselect_add(self)
return {"RUNNING_MODAL"}
def _execute(self, context):
IfcStore.classification_file = ifcopenshell.open(self.filepath)
class AddClassification(bpy.types.Operator):
class AddClassification(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.add_classification"
bl_label = "Add Classification"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
props = context.scene.BIMClassificationProperties
ifcopenshell.api.run(
"classification.add_classification",
IfcStore.get_file(),
**{"classification": Data.library_file.by_id(int(props.available_classifications))},
tool.Ifc.get(),
classification=IfcStore.classification_file.by_id(int(props.available_classifications)),
)
Data.load(IfcStore.get_file())
return {"FINISHED"}
class EnableEditingClassification(bpy.types.Operator):
@@ -67,19 +61,21 @@ class EnableEditingClassification(bpy.types.Operator):
classification: bpy.props.IntProperty()
def execute(self, context):
def callback(name, prop, data):
if name == "ReferenceTokens":
new = bpy.context.scene.BIMGeoreferenceProperties.projected_crs.add()
new.name = name
new.data_type = "string"
new.is_null = data[name] is None
new.is_optional = True
new.string_value = "" if new.is_null else json.dumps(data[name])
return True
props = context.scene.BIMClassificationProperties
props.classification_attributes.clear()
classification_data = Data.classifications[self.classification]
for attribute in IfcStore.get_schema().declaration_by_name("IfcClassification").all_attributes():
new = props.classification_attributes.add()
new.name = attribute.name()
new.is_null = classification_data[attribute.name()] is None
new.is_optional = attribute.optional()
new.data_type = "string"
if attribute.name() == "ReferenceTokens":
new.string_value = "" if new.is_null else json.dumps(classification_data[attribute.name()])
else:
new.string_value = "" if new.is_null else classification_data[attribute.name()]
blenderbim.bim.helper.import_attributes2(
tool.Ifc.get().by_id(self.classification), props.classification_attributes, callback
)
props.active_classification_id = self.classification
return {"FINISHED"}
@@ -94,34 +90,26 @@ class DisableEditingClassification(bpy.types.Operator):
return {"FINISHED"}
class RemoveClassification(bpy.types.Operator):
class RemoveClassification(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.remove_classification"
bl_label = "Remove Classification"
bl_options = {"REGISTER", "UNDO"}
classification: bpy.props.IntProperty()
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
self.file = IfcStore.get_file()
ifcopenshell.api.run(
"classification.remove_classification",
self.file,
**{"classification": self.file.by_id(self.classification)},
tool.Ifc.get(),
classification=self.file.by_id(self.classification),
)
Data.load(IfcStore.get_file())
return {"FINISHED"}
class EditClassification(bpy.types.Operator):
class EditClassification(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.edit_classification"
bl_label = "Edit Classification"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
props = context.scene.BIMClassificationProperties
attributes = {}
@@ -138,9 +126,7 @@ class EditClassification(bpy.types.Operator):
self.file,
**{"classification": self.file.by_id(props.active_classification_id), "attributes": attributes},
)
Data.load(IfcStore.get_file())
bpy.ops.bim.disable_editing_classification()
return {"FINISHED"}
class EnableEditingClassificationReference(bpy.types.Operator):
@@ -154,16 +140,7 @@ class EnableEditingClassificationReference(bpy.types.Operator):
obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
props = obj.BIMClassificationReferenceProperties
props.reference_attributes.clear()
reference_data = Data.references[self.reference]
for attribute in IfcStore.get_schema().declaration_by_name("IfcClassificationReference").all_attributes():
if attribute.name() == "ReferencedSource":
continue
new = props.reference_attributes.add()
new.name = attribute.name()
new.is_null = reference_data[attribute.name()] is None
new.is_optional = attribute.optional()
new.data_type = "string"
new.string_value = "" if new.is_null else reference_data[attribute.name()]
blenderbim.bim.helper.import_attributes2(tool.Ifc.get().by_id(self.reference), props.reference_attributes)
props.active_reference_id = self.reference
return {"FINISHED"}
@@ -180,41 +157,47 @@ class DisableEditingClassificationReference(bpy.types.Operator):
return {"FINISHED"}
class RemoveClassificationReference(bpy.types.Operator):
class RemoveClassificationReference(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.remove_classification_reference"
bl_label = "Remove Classification Reference"
bl_options = {"REGISTER", "UNDO"}
reference: bpy.props.IntProperty()
obj: bpy.props.StringProperty()
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
obj_type: bpy.props.StringProperty()
def _execute(self, context):
obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
self.file = IfcStore.get_file()
ifcopenshell.api.run(
"classification.remove_reference",
self.file,
**{
"reference": self.file.by_id(self.reference),
"product": self.file.by_id(obj.BIMObjectProperties.ifc_definition_id),
},
)
Data.load(IfcStore.get_file(), obj.BIMObjectProperties.ifc_definition_id)
Data.load(IfcStore.get_file())
return {"FINISHED"}
if self.obj_type == "Object":
if context.selected_objects:
objects = [o.name for o in context.selected_objects]
else:
objects = [context.active_object.name]
else:
objects = [self.obj]
identification = tool.Ifc.get().by_id(self.reference)[0]
for obj in objects:
ifc_definition_id = blenderbim.bim.helper.get_obj_ifc_definition_id(context, obj, self.obj_type)
element = tool.Ifc.get().by_id(ifc_definition_id)
references = ifcopenshell.util.classification.get_references(element, should_inherit=False)
for reference in references:
if reference[0] == identification:
ifcopenshell.api.run(
"classification.remove_reference",
tool.Ifc.get(),
reference=reference,
product=element,
)
break
class EditClassificationReference(bpy.types.Operator):
class EditClassificationReference(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.edit_classification_reference"
bl_label = "Edit Classification Reference"
bl_options = {"REGISTER", "UNDO"}
obj: bpy.props.StringProperty()
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
props = obj.BIMClassificationReferenceProperties
@@ -228,48 +211,47 @@ class EditClassificationReference(bpy.types.Operator):
ifcopenshell.api.run(
"classification.edit_reference",
self.file,
**{"reference": self.file.by_id(props.active_reference_id), "attributes": attributes},
reference=self.file.by_id(props.active_reference_id),
attributes=attributes,
)
Data.load(IfcStore.get_file())
bpy.ops.bim.disable_editing_classification_reference()
return {"FINISHED"}
class AddClassificationReference(bpy.types.Operator):
class AddClassificationReference(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.add_classification_reference"
bl_label = "Add Classification Reference"
bl_options = {"REGISTER", "UNDO"}
reference: bpy.props.IntProperty()
obj: bpy.props.StringProperty()
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
obj_type: bpy.props.StringProperty()
def _execute(self, context):
obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
self.file = IfcStore.get_file()
classification = None
if self.obj_type == "Object":
if context.selected_objects:
objects = [o.name for o in context.selected_objects]
else:
objects = [context.active_object.name]
else:
objects = [self.obj]
props = context.scene.BIMClassificationProperties
classification_name = Data.library_classifications[int(props.available_classifications)]
for classification_id, classification in Data.classifications.items():
if classification["Name"] == classification_name:
classification = self.file.by_id(classification_id)
classification = None
classification_name = IfcStore.classification_file.by_id(int(props.available_classifications)).Name
for element in tool.Ifc.get().by_type("IfcClassification"):
if element.Name == classification_name:
classification = element
break
ifcopenshell.api.run(
"classification.add_reference",
self.file,
**{
"reference": Data.library_file.by_id(self.reference),
"product": self.file.by_id(obj.BIMObjectProperties.ifc_definition_id),
"classification": classification,
},
)
Data.load(IfcStore.get_file(), obj.BIMObjectProperties.ifc_definition_id)
Data.load(IfcStore.get_file())
return {"FINISHED"}
for obj in objects:
ifc_definition_id = blenderbim.bim.helper.get_obj_ifc_definition_id(context, obj, self.obj_type)
if not ifc_definition_id:
continue
ifcopenshell.api.run(
"classification.add_reference",
tool.Ifc.get(),
reference=IfcStore.classification_file.by_id(self.reference),
product=tool.Ifc.get().by_id(ifc_definition_id),
classification=classification,
)
class ChangeClassificationLevel(bpy.types.Operator):
@@ -279,5 +261,28 @@ class ChangeClassificationLevel(bpy.types.Operator):
parent_id: bpy.props.IntProperty()
def execute(self, context):
getReferences(self, context, parent_id=self.parent_id)
props = context.scene.BIMClassificationProperties
props.available_library_references.clear()
for reference in IfcStore.classification_file.by_id(self.parent_id).HasReferences:
new = props.available_library_references.add()
new.identification = reference.Identification or ""
new.name = reference.Name or ""
new.ifc_definition_id = reference.id()
new.has_references = bool(reference.HasReferences)
new.referenced_source
if reference.ReferencedSource.is_a("IfcClassificationReference"):
props.active_library_referenced_source = reference.ReferencedSource.ReferencedSource.id()
else:
props.active_library_referenced_source = 0
return {"FINISHED"}
class DisableEditingClassificationReferences(bpy.types.Operator):
bl_idname = "bim.disable_editing_classification_references"
bl_label = "Disable Editing Classification References"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
props = context.scene.BIMClassificationProperties
props.available_library_references.clear()
return {"FINISHED"}
@@ -17,8 +17,9 @@
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import bpy
from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.prop import StrProperty, Attribute
from ifcopenshell.api.classification.data import Data
from blenderbim.bim.module.classification.data import ClassificationsData
from bpy.types import PropertyGroup
from bpy.props import (
PointerProperty,
@@ -31,42 +32,11 @@ from bpy.props import (
CollectionProperty,
)
classification_enum = []
def purge():
global classification_enum
classification_enum = []
def getClassifications(self, context):
global classification_enum
if len(classification_enum) < 1:
classification_enum.clear()
classification_enum.extend([(str(i), n, "") for i, n in Data.library_classifications.items()])
if classification_enum:
getReferences(self, context, parent_id=int(classification_enum[0][0]))
return classification_enum
def updateClassification(self, context):
getReferences(self, context, parent_id=int(self.available_classifications))
def getReferences(self, context, parent_id=None):
props = context.scene.BIMClassificationProperties
props.available_library_references.clear()
for reference in Data.library_file.by_id(parent_id).HasReferences:
new = props.available_library_references.add()
new.identification = reference.Identification or ""
new.name = reference.Name or ""
new.ifc_definition_id = reference.id()
new.has_references = bool(reference.HasReferences)
new.referenced_source
if reference.ReferencedSource.is_a("IfcClassificationReference"):
props.active_library_referenced_source = reference.ReferencedSource.ReferencedSource.id()
else:
props.active_library_referenced_source = 0
def get_available_classifications(self, context):
if not ClassificationsData.is_loaded:
ClassificationsData.load()
return ClassificationsData.data["available_classifications"]
class ClassificationReference(PropertyGroup):
@@ -78,9 +48,7 @@ class ClassificationReference(PropertyGroup):
class BIMClassificationProperties(PropertyGroup):
available_classifications: EnumProperty(
items=getClassifications, name="Available Classifications", update=updateClassification
)
available_classifications: EnumProperty(items=get_available_classifications, name="Available Classifications")
classification_attributes: CollectionProperty(name="Classification Attributes", type=Attribute)
active_classification_id: IntProperty(name="Active Classification Id")
available_library_references: CollectionProperty(name="Available Library References", type=ClassificationReference)
@@ -16,10 +16,17 @@
# You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import blenderbim.bim.helper
import blenderbim.tool as tool
import blenderbim.bim.module.classification.prop as classification_prop
from bpy.types import Panel, UIList
from blenderbim.bim.ifc import IfcStore
from ifcopenshell.api.classification.data import Data
from blenderbim.bim.module.classification.data import (
ClassificationsData,
ClassificationReferencesData,
MaterialClassificationsData,
CostClassificationsData,
)
class BIM_PT_classifications(Panel):
@@ -33,15 +40,15 @@ class BIM_PT_classifications(Panel):
@classmethod
def poll(cls, context):
return IfcStore.get_file()
return tool.Ifc.get()
def draw(self, context):
if not Data.is_loaded:
Data.load(IfcStore.get_file())
if not ClassificationsData.is_loaded:
ClassificationsData.load()
self.props = context.scene.BIMClassificationProperties
if Data.library_file:
if ClassificationsData.data["has_classification_file"]:
row = self.layout.row(align=True)
row.prop(self.props, "available_classifications", text="")
row.operator("bim.load_classification_library", text="", icon="IMPORT")
@@ -51,36 +58,100 @@ class BIM_PT_classifications(Panel):
row.label(text="No Active Classification Library")
row.operator("bim.load_classification_library", text="", icon="IMPORT")
for classification_id, classification in Data.classifications.items():
if self.props.active_classification_id == classification_id:
self.draw_editable_ui(classification)
for classification in ClassificationsData.data["classifications"]:
if self.props.active_classification_id == classification["id"]:
self.draw_editable_ui()
else:
self.draw_ui(classification_id, classification)
self.draw_ui(classification)
def draw_editable_ui(self, classification):
def draw_editable_ui(self):
row = self.layout.row(align=True)
row.prop(self.props.classification_attributes.get("Name"), "string_value", text="", icon="ASSET_MANAGER")
row.operator("bim.edit_classification", text="", icon="CHECKMARK")
row.operator("bim.edit_classification", text="Save changes", icon="CHECKMARK")
row.operator("bim.disable_editing_classification", text="", icon="CANCEL")
blenderbim.bim.helper.draw_attributes(self.props.classification_attributes, self.layout)
for attribute in self.props.classification_attributes:
if attribute.name == "Name":
continue
row = self.layout.row(align=True)
row.prop(attribute, "string_value", text=attribute.name)
if attribute.is_optional:
row.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="")
def draw_ui(self, classification_id, classification):
def draw_ui(self, classification):
row = self.layout.row(align=True)
row.label(text=classification["Name"], icon="ASSET_MANAGER")
if not self.props.active_classification_id:
op = row.operator("bim.enable_editing_classification", text="", icon="GREASEPENCIL")
op.classification = classification_id
row.operator("bim.remove_classification", text="", icon="X").classification = classification_id
op.classification = classification["id"]
row.operator("bim.remove_classification", text="", icon="X").classification = classification["id"]
class BIM_PT_classification_references(Panel):
class ReferenceUI:
def draw_ui(self, context):
obj = context.active_object
self.oprops = obj.BIMObjectProperties
self.sprops = context.scene.BIMClassificationProperties
self.props = obj.BIMClassificationReferenceProperties
self.file = IfcStore.get_file()
self.draw_add_ui(context)
if not self.data.data["references"]:
row = self.layout.row(align=True)
row.label(text="No References")
for reference in self.data.data["references"]:
if self.props.active_reference_id == reference["id"]:
self.draw_editable_ui()
else:
self.draw_reference_ui(reference)
def draw_add_ui(self, context):
if not self.data.data["is_available_classification_added"]:
return
row = self.layout.row(align=True)
row.prop(self.sprops, "available_classifications", text="")
if not self.sprops.available_library_references:
op = row.operator("bim.change_classification_level", text="", icon="IMPORT")
op.parent_id = int(self.sprops.available_classifications)
return
if self.sprops.active_library_referenced_source:
op = row.operator("bim.change_classification_level", text="", icon="FRAME_PREV")
op.parent_id = self.sprops.active_library_referenced_source
if self.sprops.active_library_reference_index < len(self.sprops.available_library_references):
op = row.operator("bim.add_classification_reference", text="", icon="ADD")
op.obj = self.obj
op.obj_type = self.obj_type
op.reference = self.sprops.available_library_references[
self.sprops.active_library_reference_index
].ifc_definition_id
row.operator("bim.disable_editing_classification_references", text="", icon="CANCEL")
self.layout.template_list(
"BIM_UL_classifications",
"",
self.sprops,
"available_library_references",
self.sprops,
"active_library_reference_index",
)
def draw_editable_ui(self):
row = self.layout.row(align=True)
row.operator("bim.edit_classification_reference", text="Save changes", icon="CHECKMARK")
row.operator("bim.disable_editing_classification_reference", text="", icon="CANCEL")
blenderbim.bim.helper.draw_attributes(self.props.reference_attributes, self.layout)
def draw_reference_ui(self, reference):
row = self.layout.row(align=True)
if self.file.schema == "IFC2X3":
name = reference["ItemReference"] or "No Identification"
else:
name = reference["Identification"] or "No Identification"
row.label(text=name, icon="ASSET_MANAGER")
row.label(text=reference["Name"] or "")
if not self.props.active_reference_id:
op = row.operator("bim.enable_editing_classification_reference", text="", icon="GREASEPENCIL")
op.reference = reference["id"]
op = row.operator("bim.remove_classification_reference", text="", icon="X")
op.reference = reference["id"]
op.obj = self.obj
op.obj_type = self.obj_type
class BIM_PT_classification_references(Panel, ReferenceUI):
bl_label = "IFC Classification References"
bl_idname = "BIM_PT_classification_references"
bl_options = {"DEFAULT_CLOSED"}
@@ -93,85 +164,65 @@ class BIM_PT_classification_references(Panel):
def poll(cls, context):
if not context.active_object:
return False
if not IfcStore.get_element(context.active_object.BIMObjectProperties.ifc_definition_id):
return False
return bool(context.active_object.BIMObjectProperties.ifc_definition_id)
return bool(tool.Ifc.get_entity(context.active_object))
def draw(self, context):
obj = context.active_object
self.oprops = obj.BIMObjectProperties
self.sprops = context.scene.BIMClassificationProperties
self.props = obj.BIMClassificationReferenceProperties
self.file = IfcStore.get_file()
if not Data.is_loaded:
Data.load(IfcStore.get_file())
if self.oprops.ifc_definition_id not in Data.products:
Data.load(IfcStore.get_file(), self.oprops.ifc_definition_id)
if not ClassificationReferencesData.is_loaded:
ClassificationReferencesData.load()
self.data = ClassificationReferencesData
self.obj = context.active_object.name
self.obj_type = "Object"
self.draw_ui(context)
self.draw_add_ui(context)
reference_ids = Data.products[self.oprops.ifc_definition_id]
if not reference_ids:
row = self.layout.row(align=True)
row.label(text="No References")
class BIM_PT_material_classifications(Panel, ReferenceUI):
bl_label = "IFC Material Classifications"
bl_idname = "BIM_PT_material_classifications"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "material"
for reference_id in reference_ids:
reference = Data.references[reference_id]
if self.props.active_reference_id == reference_id:
self.draw_editable_ui(reference)
else:
self.draw_ui(reference_id, reference)
@classmethod
def poll(cls, context):
if not tool.Ifc.get():
return False
try:
return bool(context.active_object.active_material.BIMObjectProperties.ifc_definition_id)
except:
return False
def draw_add_ui(self, context):
if not classification_prop.getClassifications(self.sprops, context):
return
def draw(self, context):
if not MaterialClassificationsData.is_loaded:
MaterialClassificationsData.load()
self.data = MaterialClassificationsData
self.obj = context.active_object.active_material.name
self.obj_type = "Material"
self.draw_ui(context)
name = Data.library_classifications[int(self.sprops.available_classifications)]
if name in [c["Name"] for c in Data.classifications.values()]:
row = self.layout.row(align=True)
row.prop(self.sprops, "available_classifications", text="")
if self.sprops.active_library_referenced_source:
op = row.operator("bim.change_classification_level", text="", icon="FRAME_PREV")
op.parent_id = self.sprops.active_library_referenced_source
op = row.operator("bim.add_classification_reference", text="", icon="ADD")
op.reference = self.sprops.available_library_references[
self.sprops.active_library_reference_index
].ifc_definition_id
self.layout.template_list(
"BIM_UL_classifications",
"",
self.sprops,
"available_library_references",
self.sprops,
"active_library_reference_index",
)
def draw_editable_ui(self, reference):
row = self.layout.row(align=True)
row.prop(self.props.reference_attributes.get("Name"), "string_value", text="", icon="ASSET_MANAGER")
row.operator("bim.edit_classification_reference", text="", icon="CHECKMARK")
row.operator("bim.disable_editing_classification_reference", text="", icon="CANCEL")
class BIM_PT_cost_classifications(Panel, ReferenceUI):
bl_label = "IFC Cost Classifications"
bl_idname = "BIM_PT_cost_classifications"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_cost_schedules"
for attribute in self.props.reference_attributes:
if attribute.name == "Name":
continue
row = self.layout.row(align=True)
row.prop(attribute, "string_value", text=attribute.name)
if attribute.is_optional:
row.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="")
@classmethod
def poll(cls, context):
if not tool.Ifc.get():
return False
return bool(context.scene.BIMCostProperties.cost_items)
def draw_ui(self, reference_id, reference):
row = self.layout.row(align=True)
if self.file.schema == "IFC2X3":
name = reference["ItemReference"] or "No Identification"
else:
name = reference["Identification"] or "No Identification"
row.label(text=name, icon="ASSET_MANAGER")
row.label(text=reference["Name"] or "")
if not self.props.active_reference_id:
op = row.operator("bim.enable_editing_classification_reference", text="", icon="GREASEPENCIL")
op.reference = reference_id
row.operator("bim.remove_classification_reference", text="", icon="X").reference = reference_id
def draw(self, context):
if not CostClassificationsData.is_loaded:
CostClassificationsData.load()
self.data = CostClassificationsData
self.obj = ""
self.obj_type = "Cost"
self.draw_ui(context)
class BIM_UL_classifications(UIList):
@@ -17,7 +17,9 @@
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import bpy
from blenderbim.bim.prop import StrProperty, Attribute
from ifcopenshell.util.doc import get_entity_doc
import blenderbim.tool as tool
from blenderbim.bim.prop import Attribute
from bpy.types import PropertyGroup
from bpy.props import (
PointerProperty,
@@ -31,6 +33,11 @@ from bpy.props import (
)
def get_available_constraint_types(self, context):
version = tool.Ifc.get_schema()
return [(c, c, get_entity_doc(version, c).get("description", "")) for c in ["IfcObjective"]]
class Constraint(PropertyGroup):
name: StringProperty(name="Name")
ifc_definition_id: IntProperty(name="IFC Definition ID")
@@ -46,6 +53,4 @@ class BIMConstraintProperties(PropertyGroup):
class BIMObjectConstraintProperties(PropertyGroup):
is_adding: StringProperty(name="Is Adding")
available_constraint_types: EnumProperty(
items=[(c, c, "") for c in ["IfcObjective"]], name="Available Constraint Types"
)
available_constraint_types: EnumProperty(items=get_available_constraint_types, name="Available Constraint Types")
@@ -27,7 +27,7 @@ from blenderbim.bim.ifc import IfcStore
from bpy_extras.io_utils import ImportHelper
from ifcopenshell.api.cost.data import Data
from ifcopenshell.api.unit.data import Data as UnitData
from ifcopenshell.api.resource.data import Data as ResourceData
from blenderbim.bim.module.resource.data import ResourceData
class AddCostSchedule(bpy.types.Operator):
@@ -604,7 +604,7 @@ class AddCostValue(bpy.types.Operator):
value = ifcopenshell.api.run("cost.add_cost_value", self.file, parent=parent)
ifcopenshell.api.run("cost.edit_cost_value", self.file, cost_value=value, attributes=attributes)
if parent.is_a("IfcConstructionResource"):
ResourceData.load(self.file)
ResourceData.load()
else:
Data.load(self.file)
return {"FINISHED"}
@@ -630,7 +630,7 @@ class RemoveCostItemValue(bpy.types.Operator):
cost_value=self.file.by_id(self.cost_value),
)
if parent.is_a("IfcConstructionResource"):
ResourceData.load(self.file)
ResourceData.load()
else:
Data.load(self.file)
return {"FINISHED"}
@@ -923,24 +923,26 @@ class LoadCostItemQuantities(bpy.types.Operator):
def execute(self, context):
self.props = context.scene.BIMCostProperties
self.file = IfcStore.get_file()
Data.load(self.file)
self.props.cost_item_products.clear()
self.props.cost_item_processes.clear()
self.props.cost_item_resources.clear()
ifc_definition_id = self.props.cost_items[self.props.active_cost_item_index].ifc_definition_id
for control_id, quantity_ids in Data.cost_items[ifc_definition_id]["Controls"].items():
related_object = self.file.by_id(control_id)
if related_object.is_a("IfcProduct"):
new = self.props.cost_item_products.add()
elif related_object.is_a("IfcProcess"):
new = self.props.cost_item_processes.add()
elif related_object.is_a("IfcResource"):
new = self.props.cost_item_resources.add()
new.ifc_definition_id = control_id
new.name = related_object.Name or "Unnamed"
total_quantity = 0
for quantity_id in quantity_ids:
total_quantity += self.file.by_id(quantity_id)[3]
new.total_quantity = total_quantity
if self.props.active_cost_item_index < len(self.props.cost_items):
ifc_definition_id = self.props.cost_items[self.props.active_cost_item_index].ifc_definition_id
for control_id, quantity_ids in Data.cost_items[ifc_definition_id]["Controls"].items() or {}:
related_object = self.file.by_id(control_id)
if related_object.is_a("IfcProduct"):
new = self.props.cost_item_products.add()
elif related_object.is_a("IfcProcess"):
new = self.props.cost_item_processes.add()
elif related_object.is_a("IfcResource"):
new = self.props.cost_item_resources.add()
new.ifc_definition_id = control_id
new.name = related_object.Name or "Unnamed"
total_quantity = 0
for quantity_id in quantity_ids:
total_quantity += self.file.by_id(quantity_id)[3]
new.total_quantity = total_quantity
return {"FINISHED"}

Some files were not shown because too many files have changed in this diff Show More