Compare commits

...

508 Commits

Author SHA1 Message Date
Dion Moult ed4abacd1e Fix #3578. Support querying surface styles in tools like IfcCSV. 2023-08-14 11:55:15 +10:00
Dion Moult 8bf55ab46c See #3561. Misc annotation decorator now only highlights non-internal edges. 2023-08-13 23:34:06 +10:00
Dion Moult 7edabce657 Fix #3562. Non-manifold geometry is no longer considered in join criteria as there is no such thing as a closed polygon. 2023-08-13 23:04:32 +10:00
Sigma Dimensions (Yass) 56187e4da3 Improve Cost Schedule Layout 2023-08-13 13:23:31 +01:00
Sigma Dimensions (Yass) 31a12faef1 woopsies 2023-08-13 12:18:48 +01:00
Sigma Dimensions (Yass) eb94c16465 fix the bug fix #07b5c49 - where counting objects controlled by a cost item stopped working 2023-08-13 12:16:29 +01:00
Dion Moult e8437a9c54 Fix #3253. Bug where duplicating elements with fillings incorrectly recreated fillings at the cursor (i.e. the cursor was used as a target destination).
Now, instead of trying to be clever and regenerating filling locations and opening types, we simply copy the existing scenario. This also means it'll be more stable for weird fillings coming from proprietary apps.
2023-08-13 20:07:33 +10:00
Dion Moult 840039ef1e Fix #3472. You can now toggle selectability of linked IFCs. 2023-08-13 15:49:23 +10:00
Dion Moult 84a749605e See #3579. Support Google Colab as an installation option. 2023-08-13 14:48:49 +10:00
Bruno Postle c1283d2ab0 fix SyntaxError: '(' was never closed 2023-08-12 21:36:40 +01:00
Sigma Dimensions (Yass) 07b5c4918b fix api logic where only quantities of the same type can be assignmed to a cost control 2023-08-12 15:17:32 +01:00
Sigma Dimensions (Yass) 703f0a6b70 fix orphaned data when unassigning element quantities from cost item control 2023-08-12 14:57:01 +01:00
Dion Moult 5e98dee46f Fix #3554. Support editing IfcIndexedPolyCurve profiles where IfcLineIndex has >2 indices. 2023-08-12 22:25:34 +10:00
Sigma Dimensions (Yass) b2eddf55d2 Fix contracting/expanding cost schedule tree and add tests to avoid proof changes 2023-08-12 12:58:16 +01:00
Massimo Fabbro db7be640d4 Fix #3569 Variable name override caused the bug (#3575) 2023-08-12 19:03:58 +10:00
Dion Moult 04fa597844 Fix #3564. Bug where IfcBuiltSystems didn't show up due to missing icon in IFC4X3. 2023-08-12 17:09:48 +10:00
Dion Moult 694dbeb96c Fix #3576. Explicit drawing annotations are now immune to "include / exclude" filters. 2023-08-12 17:06:22 +10:00
Dion Moult 7afd358c3c Fix #3556. Bug where quantities could not be imported using IfcCSV. Also make IfcCSV support any qset name, not just "Qto" prefixed names. 2023-08-12 16:41:57 +10:00
Dion Moult b0a5a570a9 IfcCSV now uses the new filter query system. 2023-08-12 16:40:59 +10:00
Dion Moult c45b84c850 Fix #3577. Don't force uppercase in schedules. 2023-08-12 16:16:51 +10:00
rileywong311 d575a43aee Make refresh Brick viewer refresh both screens 2023-08-11 16:14:41 -07:00
rileywong311 0b151ea559 Second level query for Brick equipment+point roots 2023-08-11 16:14:41 -07:00
rileywong311 926970b256 Polish Brick UI and add "last saved" label 2023-08-11 16:14:41 -07:00
rileywong311 d58af0bfa5 Filter Brick class if deprecated 2023-08-11 16:14:41 -07:00
rileywong311 429da7c6f3 Filter more (all) Brick ontology namespaces
I'm not sure if this method of filtering out the entire domain will cause problems. This could use investigation.
2023-08-11 16:14:41 -07:00
rileywong311 60a7433573 Clear Brick breadcrumbs on close project 2023-08-11 16:14:41 -07:00
rileywong311 08a5d444d4 Remove another needless BrickStore.graph.triples check
As before, this returns a generator type which always evaluates true, even if it is empty.
2023-08-11 16:14:41 -07:00
rileywong311 1982069b69 Add description to Brick operators 2023-08-11 16:14:41 -07:00
rileywong311 7c06c65df3 Move Brick "set root" button and change roots on update instead of operator 2023-08-11 16:14:41 -07:00
Sigma Dimensions (Yass) 6263766202 fix Cost schedule of rates UI bug where linked types dont't refresh correctly (and black formatting) 2023-08-11 14:57:43 +01:00
Dion Moult 66fbbeff54 See #3561. Fix bug where schedules couldn't be removed. 2023-08-11 22:47:42 +10:00
Dion Moult 3c5ad1328f Auto create relevant context when attempting to add an annotation to make it friendlier for new users. 2023-08-11 22:41:47 +10:00
Dion Moult 3345d7e088 Fix #2894. Implement reflected ceiling plans. Note that this requires new bot build. 2023-08-11 22:41:47 +10:00
Dion Moult 2bb25cb874 See #3501. Minor matrix math fix that caused annotations to not appear in global coordinates. 2023-08-11 22:41:47 +10:00
Dion Moult 444a728e7a Improve default search filter to include types and spatial elements. 2023-08-11 22:41:47 +10:00
Sigma Dimensions (Yass) 70bea0c865 Feature to add a camera to extents of the scene objects 2023-08-11 13:39:58 +01:00
Sigma Dimensions (Yass) 6eea1e9035 feature to clear previous 4D Animation (object keyframes & colors) 2023-08-11 13:36:14 +01:00
Kristoffer Andersen 4deb1836ad Update bld.bat
reduce number of schema variations on windows as well
2023-08-11 11:42:08 +02:00
Kristoffer Andersen 4c8007dcfe Update meta.yaml
pin compatible boost, occt and cgal-cpp
2023-08-11 08:44:25 +02:00
Kristoffer Andersen 000116bb3d Update build.sh
limit number of schema variants
2023-08-11 08:22:52 +02:00
Dion Moult a2b6f21c7a See #2894. Implement SVG mirroring options (typically used for RCPs or useful when converting to other things like DXF). 2023-08-11 16:01:01 +10:00
Sigma Dimensions (Yass) a374ee39d3 simplify setup_default_task_columns 2023-08-11 02:06:49 +01:00
Sigma Dimensions (Yass) 0f5c82dffd animation settings now use human readable durations to make it less confusing 2023-08-11 02:05:37 +01:00
Sigma Dimensions (Yass) 268fed761e simplify updateTaskDuration 2023-08-11 02:03:07 +01:00
Sigma Dimensions (Yass) fe01d252db WIP - Scheduling UI improvements 2023-08-10 22:07:34 +01:00
Sigma Dimensions (Yass) dc36d74337 Hide Header for Costing and Schedule Tab 2023-08-10 16:36:35 +01:00
Sigma Dimensions (Yass) 7a0da7bd8e Revert "Rename and move sub panels of Costing and Schedule Tab"
This reverts commit 9d4715c0a9.
2023-08-10 16:20:13 +01:00
Andrej730 530d5a4e63 plug add_transition to fit_flow_segments 2023-08-10 17:37:52 +05:00
Andrej730 2d847d1704 Adjusting connected elements on bim.regenerate_distribution_element
Basically commented out automatic adjustment after 45f81b478 - now it's hapenning only when you decide to regenerate it.

It's adjusting connected segments extrusion and location, for all other elements besides segments it's adjusting only location. The idea is it will try to change as less as possible.

Demo - https://imgur.com/oZ1I2Bl
2023-08-10 16:31:08 +05:00
Andrej730 631d8fcfb1 quickly connect mep elements if they have matching ports
Demo - https://imgur.com/a/o9u8M1u
2023-08-10 15:06:16 +05:00
Andrej730 a8888efb88 fixed bug getting port relating element in ifc2x3 2023-08-10 14:12:20 +05:00
Andrej730 a51ecea9b1 More descriptive error message on attempt to delete drawings category 2023-08-10 13:55:46 +05:00
Andrej730 4d49517111 small refactor 2023-08-10 13:49:35 +05:00
Thomas Krijnen d5d1b3567b Update draw.py --include-curves 2023-08-10 15:44:21 +08:00
Kristoffer Andersen 7827ce3448 Update build.sh
fix env var declaration
2023-08-10 08:22:07 +02:00
Kristoffer Andersen a05059db01 Update build.sh
Reduce number of schema versions
2023-08-10 08:06:38 +02:00
Thomas Krijnen b78490ca56 #3510 Sample points along diagonal as well for quick and dirty occlusion detection 2023-08-10 10:52:06 +08:00
Andrej730 f8ffc97bf5 remove_product to also remove psets for types 2023-08-09 16:37:47 +05:00
Andrej730 29d4a8d5fe More Ports UI
Created more UI for ports.

Now you can see the list of ports from UI and what objects they are connected to, you can quickly select any ports/connected objects or disconnect objects.

From port UI you can see what object's it located on and what object it's connected to and quickly jump across them.

Demo - https://imgur.com/a/Gu4Vz7V
2023-08-09 15:04:27 +05:00
Andrej730 2c1c13f27e bim.add_transition
Added simple operator to add transition between two mep segments (now only rectangular collinear segments are supported).
It also reuses the transition type that was previously used to connect segments of the same type.

Demonstration - https://imgur.com/a/c1AOxj1
2023-08-09 15:04:27 +05:00
Andrej730 4dc55e844c Fix default mep profile names
Previously it was generating names like 4000000x4000000 in mm projects.
2023-08-09 14:46:58 +05:00
Thomas Krijnen b611151039 #3536 fix edge segments in world coords 2023-08-09 14:55:23 +08:00
Dion Moult 9fc32e5c2e IfcTester JSON reporter now also stores identification data separately for convenient reporting. 2023-08-09 16:42:03 +10:00
Dion Moult c5e5036fd3 Store applicability in IfcTester JSON results 2023-08-09 15:00:58 +10:00
Dion Moult 0f68679295 Fix #2753. Disable mapping representations when appending assets via the ExtractElements recipe to be more forgiving (and faster) on invalid models. 2023-08-09 00:13:16 +10:00
Dion Moult fea0d93162 Fix SurfaceColour typo that broke loading colours in some IFCs 2023-08-08 23:47:57 +10:00
Dion Moult e43347161a Fix #3547. Bug where using existing clippings were not preserved when editing profile or wall representations. 2023-08-08 22:35:40 +10:00
arun 706aa70899 Update create_2pt_wall.py (#3553)
* Update create_2pt_wall.py

included unit scale for use cases other than si unit

* Revised Update create_2pt_wall.py

included is_si kwarg to select between si units and other units and also changed the dtype of p1 and p2 array to float incase they are entered as integers
2023-08-08 13:06:36 +10:00
Carlos Dias fe5a6749a7 Add element guId in BCF report (#3549) 2023-08-06 21:11:31 +10:00
Thomas Krijnen c3f352c018 #3546 Filter duplicate edge indices 2023-08-06 14:27:31 +08:00
Dion Moult 79584dba13 Whoops 2023-08-04 23:56:05 +10:00
Dion Moult 1c74147d89 Dissolve specifically IOS triangulated edges for meshes instead of generic Blender limited dissolve. 2023-08-04 23:49:50 +10:00
Dion Moult cc53744c29 Skeleton operator to regenerate distribution elements recursively 2023-08-04 17:20:50 +10:00
Dion Moult 945836f578 Initial implementation of scale dependent map conversion for IFC4X3 2023-08-04 14:42:43 +10:00
rileywong311 e88e2f5d9c Add brick create relation with split screen 2023-08-03 16:41:30 -07:00
rileywong311 ea267d9443 Implement Brick split screen 2023-08-03 16:41:30 -07:00
rileywong311 d64e4d70c5 Add more subclasses to Brick root options 2023-08-03 16:41:30 -07:00
rileywong311 156844b27d Move Brick list root setting 2023-08-03 16:41:30 -07:00
rileywong311 b96944ff1b Reformat Brick props order and singularize "brick_entity_classes" 2023-08-03 16:41:30 -07:00
rileywong311 7bfa6115ee Refactor AddBrickRelation to combine object namespace+label in operator
This is an important change that should make adding relations through other methods easier to implement
2023-08-03 16:41:30 -07:00
rileywong311 589b3928f3 Guard loading brick files from bad paths
Referred to "IFCFileSelector.is_existing_ifc_file" method for to make this
2023-08-03 16:41:30 -07:00
rileywong311 a884eb5c54 Add "rdfs:label" to Brick relation options
This is a tricky one because "rdfs:label" cannot really be queried from the Brick ontology, so it is a special case that probably needs to get added manually. Furthermore, an entity really shouldn't have multiple labels, so the option to give an entity an "rdfs:label"
 should only appear if it doesn't have one already. The way I did it meant I had to manually reset the Enum type in Blender because it was the active one selection and made the Enum blank since it got removed  now that the entity has a "rdfs:label." One possible alternative solution is to allow multiple labels, but in the query to import bricks in the UI list only select one label somehow.
2023-08-03 16:41:30 -07:00
rileywong311 796d19a3a8 Change BrickschemaData.relations to BrickschemaData.active_relations 2023-08-03 16:41:30 -07:00
rileywong311 b9211e2a93 Load Brick relationships from schema 2023-08-03 16:41:30 -07:00
rileywong311 b662d6f08e Remove if Brickstore.graph.triples check
This returns a generator type which I believe is always truthy, so it didn't change anything.
2023-08-03 16:41:30 -07:00
rileywong311 deecd60ff1 Implement basic Brick create relation 2023-08-03 16:41:30 -07:00
rileywong311 01615724fd Add "remove brick relation" operator 2023-08-03 16:41:30 -07:00
rileywong311 97685a7a77 Rename Brick "attributes" to "relations" 2023-08-03 16:41:30 -07:00
Dion Moult dc1721ed60 Fix #3538. Update bl_info metadata to reflect that we are a native IFC authoring platform not an import-export add-on. 2023-08-03 23:25:13 +10:00
Andrej730 e801ba49b2 option to provide step_id into bim.create_shape_from_step_id directly 2023-08-03 15:54:47 +05:00
Andrej730 2446479d82 remove_product to remove object placement too
previously it wasn't deleting object placement when you removed the objects (and related elements like IfcDirections etc) and working on project long enough you might had a bunch of orphaned data in it
2023-08-03 15:54:47 +05:00
Andrej730 8908011ecf automatically assign ports predefined type 2023-08-03 13:42:00 +05:00
Andrej730 fe6be61d7a small change for readibility 2023-08-02 16:37:09 +05:00
Andrej730 45f81b4780 adjust obstruction position on changing segment length
Now when you change the segment's length connected obstructions will adjust. Same for changing obstruction's length. You can also move obstructions/segment around and then use "Regenerate" to make them fit.
Small demo - https://imgur.com/a/atm8Mdx
2023-08-02 16:33:36 +05:00
Andrej730 92df1b351c add_obstruction to fit_flow_segments, derive it's position from cursor 2023-08-02 14:09:25 +05:00
Andrej730 002c7c1ad9 Fixed #3533 (also fixed the same issue for windows) 2023-08-02 11:42:48 +05:00
Andrej730 a583860b58 Fixed error on drawing tester UI
Error was:
Traceback (most recent call last):
  File "\3.6\scripts\addons\blenderbim\bim\module\tester\ui.py", line 39, in draw
    row.prop(self.props, "should_load_from_memory")
AttributeError: 'BIM_PT_tester' object has no attribute 'props'
2023-08-02 11:41:37 +05:00
Dion Moult 4efe198dbb Whoops 2023-08-02 00:03:24 +10:00
Dion Moult 7a6332c883 Skeleton of "add fitting" operator which decides appropriate fitting type 2023-08-02 00:02:05 +10:00
Andrej730 a14ef1ebc2 Fixed TestGetApplicableTypes after 0e69f484f 2023-08-01 18:24:11 +05:00
Andrej730 fab0f92c8a keep the default flow direction for ports NOTDEFINED as it's not yet set 2023-08-01 17:52:01 +05:00
Andrej730 490937ecf9 operator to add mep obstruction
Currently it's not yet available from UI, only from F3 menu.
On changing segment's length obstruction is not yet adjusted.
Short demonstration - https://imgur.com/a/I5fybpO
2023-08-01 17:45:17 +05:00
Andrej730 cbf8b7f3a2 root.remove_product to remove IfcRelConnectsPorts too
previously if you deleted some flow segment that was connected to another the connection would stay resulting in invalid ifc and other bugs
2023-08-01 17:45:17 +05:00
Andrej730 6c5bc5a3e8 Poll check for show/hide ports
https://i.imgur.com/NFAM1UC.png
2023-08-01 17:45:17 +05:00
Andrej730 f7aa67c6e9 Communicate that blend file is also saved on Ctrl-S
https://i.imgur.com/BnzTDhM.png
2023-08-01 17:45:17 +05:00
Andrej730 4ce41b33bb small add_constr_type_instance refactor
ifc_class removed as it was never used - ifc_class was always figured either from BIMModelProperties.relating_type_id or operator.relating_type_id
2023-08-01 17:45:17 +05:00
Dion Moult a0fd2a11cb Another minor documentation fix 2023-08-01 22:43:40 +10:00
Dion Moult 4443f484c1 Easily create a "default" duct / pipe even if you have no demo library. 2023-08-01 22:34:34 +10:00
Dion Moult 120a9f9f3e Minor documentation fix 2023-08-01 22:25:25 +10:00
Andrej730 e33a400ebb Fixed error opening type manager from window_tool, slab_tool etc
Th error was:

Error: Python: Traceback (most recent call last):
  File "\scripts\addons\blenderbim\bim\module\model\ui.py", line 54, in invoke
    props.type_class = props.ifc_class
TypeError: bpy_struct: item.attr = val: enum "" not found in ('IfcActuatorType', 'IfcAirTerminalBoxType', 'IfcAirTerminalType', 'IfcAirToAirHeatRecoveryType', 'IfcAlarmType', 'IfcAudioVisualApplianceType', 'IfcBeamType', 'IfcBoilerType', 'IfcBuildingElementPartType', 'IfcBuildingElementProxyType', 'IfcBurnerType', 'IfcCableCarrierFittingType', 'IfcCableCarrierSegmentType', 'IfcCableFittingType', 'IfcCableSegmentType', 'IfcChillerType', 'IfcChimneyType', 'IfcCivilElementType', 'IfcCoilType', 'IfcColumnType', 'IfcCommunicationsApplianceType', 'IfcCompressorType', 'IfcCondenserType', 'IfcControllerType', 'IfcCooledBeamType', 'IfcCoolingTowerType', 'IfcCoveringType', 'IfcCurtainWallType', 'IfcDamperType', 'IfcDiscreteAccessoryType', 'IfcDistributionChamberElementType', 'IfcDistributionElementType', 'IfcDoorStyle', 'IfcDoorType', 'IfcDuctFittingType', 'IfcDuctSegmentType', 'IfcDuctSilencerType', 'IfcElectricApplianceType', 'IfcElectricDistributionBoardType', 'IfcElectricFlowStorageDeviceType', 'IfcElectricGeneratorType', 'IfcElectricMotorType', 'IfcElectricTimeControlType', 'IfcElementAssemblyType', 'IfcEngineType', 'IfcEvaporativeCoolerType', 'IfcEvaporatorType', 'IfcFanType', 'IfcFastenerType', 'IfcFilterType', 'IfcFireSuppressionTerminalType', 'IfcFlowInstrumentType', 'IfcFlowMeterType', 'IfcFootingType', 'IfcFurnishingElementType', 'IfcFurnitureType', 'IfcGeographicElementType', 'IfcHeatExchangerType', 'IfcHumidifierType', 'IfcInterceptorType', 'IfcJunctionBoxType', 'IfcLampType', 'IfcLightFixtureType', 'IfcMechanicalFastenerType', 'IfcMedicalDeviceType', 'IfcMemberType', 'IfcMotorConnectionType', 'IfcOutletType', 'IfcPileType', 'IfcPipeFittingType', 'IfcPipeSegmentType', 'IfcPlateType', 'IfcProtectiveDeviceTrippingUnitType', 'IfcProtectiveDeviceType', 'IfcPumpType', 'IfcRailingType', 'IfcRampFlightType', 'IfcRampType', 'IfcReinforcingBarType', 'IfcReinforcingMeshType', 'IfcRoofType', 'IfcSanitaryTerminalType', 'IfcSensorType', 'IfcShadingDeviceType', 'IfcSlabType', 'IfcSolarDeviceType', 'IfcSpaceHeaterType', 'IfcSpaceType', 'IfcSpatialZoneType', 'IfcStackTerminalType', 'IfcStairFlightType', 'IfcStairType', 'IfcSwitchingDeviceType', 'IfcSystemFurnitureElementType', 'IfcTankType', 'IfcTendonAnchorType', 'IfcTendonType', 'IfcTransformerType', 'IfcTransportElementType', 'IfcTubeBundleType', 'IfcUnitaryControlElementType', 'IfcUnitaryEquipmentType', 'IfcValveType', 'IfcVibrationIsolatorType', 'IfcWallType', 'IfcWasteTerminalType', 'IfcWindowStyle', 'IfcWindowType')

WARN (bpy.rna): C:\Users\blender\git\blender-v360\blender.git\source\blender\python\intern\bpy_rna.c:1343 pyrna_enum_to_py: current value '8' matches no enum in 'BIMModelProperties', '', 'ifc_class'
2023-08-01 14:19:30 +05:00
Andrej730 2cc9ea0539 tests for system.get_connected_to / get_connected_from 2023-08-01 13:40:06 +05:00
Andrej730 3c8dc70198 error message on setting flow direction for disconnected port
previously it just did nothing which was a bit confusing
2023-08-01 13:40:06 +05:00
Chetan 08d8657588 When creating a GLTF file, make the material double sided to fix a bug where some surfaces were transparent. This will allow surfaces created from a mesh to be be viewed from all angles regardless of the shape of the mesh or the directions of the normals. 2023-08-01 08:46:57 +08:00
Dion Moult e0b4c1a810 Flow segments are now created at a specific RL above the active storey in the BIM Tool 2023-07-31 23:32:10 +10:00
ay-ex a517c9338d fix_choco_cmake_url (#3528)
fixed problematic url for blender cmake
2023-07-31 22:50:26 +10:00
Andrej730 ef01766f4d fixed bug in system.get_connected_to / get_connected_from
import was missing
2023-07-31 17:30:40 +05:00
Andrej730 e9f77f8d2e Create empty at object placement from debug panel
Small feature for debugging - https://i.imgur.com/OSOCIqG.png
2023-07-31 16:00:44 +05:00
Andrej730 a93d4da810 Fixed #3262 2023-07-31 15:17:45 +05:00
Andrej730 e86d3731ff sort keys in the entity to type mapping 2023-07-31 11:46:27 +05:00
Andrej730 0e69f484f4 More general solution for #3466 2023-07-31 11:45:03 +05:00
Dion Moult 3a812a9ec1 Fix #3465. Move representation utilities into new tab system. 2023-07-30 23:18:58 +10:00
Dion Moult 12d70324b8 Fix #3483. You can now conveniently duplicate material layers. 2023-07-30 22:41:28 +10:00
Bruno Postle caff4d0b20 Fix some broken restructured text formatting 2023-07-30 12:41:45 +01:00
Dion Moult afb9aa178f Fix #3453. Hidden annotation type removed, and demo library now comes with linetypes for dashed, fine, thin, medium, thick, and strong. 2023-07-30 21:13:21 +10:00
Wei Yang 356a10eac9 Add UV texture support for IfcTessellatedFaceSet (#3523)
* Add texture support for IfcIndexedTextureMap

* fix description.

* Fix posible duplicate coordinate index issue

* Clean up code.

* revert default clean_mesh.

* Fix errors.

* change to EXTEND mode when not RepeatS/T

* remove tris merge to support mix quads and tris.
2023-07-30 20:34:50 +10:00
Massimo Fabbro 60f60798df Add new api function that allows to create arbitrary profiles with voids (#3522) 2023-07-30 17:30:56 +10:00
Dion Moult 360729738c Fix #3466. Bug in upstream IFC where distribution elements and furnishing elements had no corresponding type and occurrence. 2023-07-30 16:59:25 +10:00
Dion Moult 794212c223 Fix #2906. Basic support added for perspective drawings. 2023-07-30 16:50:17 +10:00
Dion Moult 2015f0618a Fix #3484. You can now have project-specific pset templates distributed with your project in a relative path. Defaults to pset/. 2023-07-30 14:47:05 +10:00
Dion Moult d7158be431 Purge obsolete search operators in favour of new search filters, colour legend system. 2023-07-30 08:16:30 +10:00
Dion Moult 1ccbe831c8 Purge select similar prop operator in favour of dedicated select similar panel 2023-07-30 08:03:37 +10:00
Dion Moult 5a8e4fa8fd Purge obsolete & risky module unloading / loading feature in favour of tabs. 2023-07-30 07:55:04 +10:00
Dion Moult 9048b67724 Fix #3501. Annotation tool now creates annotations at the 3D cursor. 2023-07-29 23:46:45 +10:00
Dion Moult 9059b5d7ec Fix #3511. You can now use a query to load in occurrences from another project (library). 2023-07-29 22:43:50 +10:00
Dion Moult 7af3f89688 Fix #3502. New select similar feature. 2023-07-29 20:46:07 +10:00
Dion Moult 8be0f2e1da Fix #3273. Don't use simple string replacement when renaming drawings but instead identify elements by attributes which is preferred. 2023-07-29 14:46:11 +10:00
Thomas Krijnen 630bb88a15 SVG serialization UnifyInputs option 2023-07-29 10:07:33 +08:00
Thomas Krijnen 6575921ccd Implement IfcGridPlacement 2023-07-29 10:06:44 +08:00
Thomas Krijnen 95eb78e919 ci-ifctester-pypi.yml Add workflow_dispatch event 2023-07-29 09:37:39 +08:00
Thomas Krijnen 78b4b09be9 Include .xsd in ifctester package pypi distribution 2023-07-29 09:34:26 +08:00
Dion Moult 32e02db3f7 Fix #3521. Forgot to commit function. 2023-07-29 10:21:19 +10:00
Sigma Dimensions (Yass) 9d4715c0a9 Rename and move sub panels of Costing and Schedule Tab 2023-07-28 19:04:12 +01:00
Dion Moult 0ba5db751c Fix #3471. Allow to select hidden types (except if the type is excluded). 2023-07-28 23:42:02 +10:00
Dion Moult c1a15d944f Fix #3518. Default to deeper opening extrusions to handle more wall thicknesses. 2023-07-28 23:07:56 +10:00
ay-ex 7cd1c47ceb add ids html report template percentage bars per section (#3520)
adding html report template percentage bars to each section
2023-07-28 18:55:35 +10:00
Dion Moult a1aa047161 Revert "Example of UI if we sentence-cased IFC classes. Good or bad? Please comment at https://community.osarch.org/discussion/comment/16493"
This reverts commit 53bd4bf5ea.
2023-07-28 17:42:41 +10:00
Dion Moult e4896539b4 Fix #3509. Backport Qto_BodyGeometryValidation to IFC4 (via EQto) and fix bug where there was no way to have custom Qtos show up in the qto dropdown. 2023-07-28 17:41:36 +10:00
Dion Moult 47ee667f06 Fix #3517. Fix bug where edit arbitrary profile button doesn't show if your profile has voids. 2023-07-28 17:27:54 +10:00
Dion Moult 7804c9ea80 Potential fix for #3273 where renamed drawings might not get moved on Windows. 2023-07-28 15:35:02 +10:00
Dion Moult 3980666824 Fix #3513. Minor issue where new hierarchical drawing list broke print all feature. 2023-07-28 14:55:22 +10:00
Dion Moult eda0b33b41 Fix #3512. Fix incorrect shapely fills due to unclosed polygons (tolerance issue) and incorrect raycast results. 2023-07-28 12:56:42 +10:00
Ryan Schultz 8fdd0983c0 Merge branch 'v0.7.0' of https://github.com/IfcOpenShell/IfcOpenShell into v0.7.0 2023-07-27 18:34:40 -05:00
Ryan Schultz 0ef141447b added to ifc_classes_suggestions.json 2023-07-27 18:34:27 -05:00
Bruno Postle 06ea0512f5 BlenderBIM filter Git messages, remove sort #3096 2023-07-27 16:35:10 +01:00
Dion Moult 8af11db8c2 Fix #3006. Don't affect visibility of types when activating drawing. 2023-07-27 22:29:17 +10:00
Dion Moult 1c5a4ddf20 Fix #3223. Fix #3474. When creating new representations, give the option to generate bounding box or "outline" curves (60 degree crease threshold hardcoded) instead of full 3D mesh. 2023-07-27 12:36:15 +10:00
ay-ex b792b05753 add collapsible detail results (#3508) 2023-07-26 23:33:36 +10:00
Dion Moult 524bace23b Redo mechanism that allows filtered BIM Tool classes to be less "magic" and more explicit whether it filters all vs a specific element type. 2023-07-26 18:14:31 +10:00
Dion Moult 0d656040eb Fix #3479. Regression in 69fa124 when addressing #3421 that led to objects with multiple paths not taking part in the wall / slab joining procedure. 2023-07-25 23:29:06 +10:00
Dion Moult 12fcdebd37 Fix #3486. If Z index isn't specified, text is prioritised over other annotation types. 2023-07-25 22:50:09 +10:00
ay-ex c798309039 fix choco url constants (#3504) 2023-07-25 22:35:25 +10:00
Dion Moult e1913bf03e Fix #3497. Set closed vs unclosed polygonal face sets to allow loading in some IFC software. 2023-07-25 17:41:33 +10:00
Dion Moult bc541d335f Fix #3469. Fix bug where you couldn't load models if there was no style due to no polygons. 2023-07-25 17:12:11 +10:00
rileywong311 d4933c7f24 Refactor BrickStore.root_classes 2023-07-24 21:27:44 -07:00
Trashman247 4e96f17dac Ignore populate Brick attributes if active entity is a brick entity 2023-07-24 21:27:44 -07:00
Trashman247 4665b44ded Make Brick "root_classes" one source of truth 2023-07-24 21:27:44 -07:00
Trashman247 d834d94fd9 Change brick attributes query "name" and "value" to "predicate" and "object" 2023-07-24 21:27:44 -07:00
Trashman247 f2db071cf3 Move brick entity classes to BrickStore and create drop-down for entity type creation 2023-07-24 21:27:44 -07:00
Trashman247 0348e6d11a Move Brick namespace from data to BrickStore 2023-07-24 21:27:44 -07:00
Trashman247 1eb7c006f1 Add Brick "Set View" operator to change list hierarchy
- "System" as a root may need different logic since children of a system are not "subClasses" but rather "parts". Currently, "part" would be listed as attribute rather than a direct object in the group.
2023-07-24 21:27:44 -07:00
Trashman247 c80a2e1d64 Reformat BrickSchema UI 2023-07-24 21:27:44 -07:00
Dion Moult 7b2d99ba1e Fix #3498. 2023-07-25 08:54:45 +10:00
Dion Moult 55c4ce0071 Fix #3391 #3491 #3493. New space generation that uses a cutting plane across all eligible objects instead of a simplified "straight wall" analysis.
This makes generating spaces significantly more robust, makes it easier to add / remove "space separating objects" (so we now have support for virtual elements), and only acts on visible objects.
2023-07-24 23:22:12 +10:00
Dion Moult 8e5dc01758 New projects now load with a Views and Types collection with appropriate visibility settings. 2023-07-24 18:01:25 +10:00
Dion Moult 90632a363c Demo library now ships with example text annotation types (door tag, window tag, etc) 2023-07-24 17:59:04 +10:00
Dion Moult 4d6225bc32 More explanatory images in geometry creation docs 2023-07-23 23:56:50 +10:00
Dion Moult 411ec36e65 Add documentation specifically about swept disk solid geometry for rebar 2023-07-23 23:41:53 +10:00
Gorgious 1a54f54c64 You can now directly apply an ifc array modifier
Previously one would have to click on "remove" then "keep objects"
2023-07-23 15:13:04 +02:00
Dion Moult 5d48dc15c1 Finish writing documentation on geometry creation 2023-07-23 23:04:14 +10:00
Gorgious 5169599884 Array children are now transform-unlocked & unconstrained when the array is applied 2023-07-23 14:01:17 +02:00
Gorgious c3323733a7 Array children are now transforme-locked after editing the array
I have yet to find how to set it on ifc load though
2023-07-23 13:39:52 +02:00
Gorgious 1468b7fddd Array children now follow their parent with a "child-of" constraint #3492 2023-07-23 13:38:37 +02:00
Gorgious 41d6882b40 Clicking on the "Select all array objects" button now sets the parent object as active #3492
Moved the array objects fetching code to tool.Blender
Also a bit of housekeeping and fixing minor typos here and there
2023-07-23 13:03:06 +02:00
Dion Moult a1ebc2e167 Revert "Small tester UI fix #3487"
This reverts commit c7a1acf974.
2023-07-23 19:09:00 +10:00
Carlos Dias d5d9c91f5a [BlenderBIM] Update IfcTester (2023_07_21) (#3489) 2023-07-23 19:04:45 +10:00
Ryan Schultz 98a6cbb1aa Allow negative X_angle values 2023-07-23 19:04:12 +10:00
Dion Moult f48060e7c7 Fix #3488. Minor regression in IFC4X3 mapping. 2023-07-23 15:42:09 +10:00
Dion Moult dd827bb4c5 You can now save, load, and create custom colour legends of properties 2023-07-23 15:20:14 +10:00
Dion Moult b1cab9484c Search now renamed filtering and grouping. New "sandbox" section for more experimental panels. Old selector moved to sandbox along with augin/lca/covetool/temporary cutaways also moved there. 2023-07-22 23:09:48 +10:00
Dion Moult 74637dd0ea You can now load and save searches from the search panel 2023-07-22 22:49:22 +10:00
Dion Moult a61eae5f98 More powerful search interface that uses the new facet selector syntax 2023-07-22 16:48:36 +10:00
Andrej730 4c18a89ee6 Spatial manager elevation in projects units and some bug fixed
1) Elevation prop now is taking into account project units (previously it was hardcoded to meters)
2) Fixed a bug that occured when you started changing spatial element name/elevation right after you loaded spatial manager.

https://i.imgur.com/1keMGus.png
2023-07-22 11:03:22 +05:00
Andrej730 4a7b8f1104 Fixed errors after b6ddc0d
`element.is_a() in ...` wasn't taking into account spatial classes that inherit from IfcSpatialElement/IfcSpatialStructureElement
2023-07-22 10:36:40 +05:00
Andrej730 c7a1acf974 Small tester UI fix #3487 2023-07-21 18:00:11 +05:00
Andrej730 630ac7250a Fixed ifctester tests by the latest ids schema #3458
Commented out tests involving minOccurs and maxOccurs until https://github.com/buildingSMART/IDS/issues/144 gets resolved.
2023-07-21 15:33:28 +05:00
Dion Moult 08756a24a4 Fix #3477. Move text panels to object information tab. 2023-07-21 18:14:49 +10:00
Gorgious56 8edee78900 Minor fix in wall creation code 2023-07-21 09:57:23 +02:00
Dion Moult 9864261c41 New wall tool, slab tool, etc in toolshelf to improve usability. 2023-07-21 17:29:09 +10:00
Andrej730 bb3e978bba moving code to shape builder 2023-07-21 11:04:44 +05:00
Gorgious56 288cc698bd #3478 : Ifc class name suggestions can now autofill predefined type
If they are correctly filled in the json file :)
2023-07-20 19:40:31 +02:00
Andrej730 a2e70cb454 fixed error after 190153347 2023-07-20 21:56:28 +05:00
Andrej730 1901533470 Distance subtype for rl1, rl2 and x/y/z (opening dimensions) in BIM Tool 2023-07-20 19:35:35 +05:00
Andrej730 c61854c7aa small matrix math refactor
more readable and allows avoiding jumping between 3d/4d vectors
2023-07-20 19:13:56 +05:00
Andrej730 0dee353b8e A bit more descriptive port UI
Before - https://i.imgur.com/ZhVRoJM.png
After - https://i.imgur.com/tI1h2i7.png
2023-07-20 18:08:23 +05:00
Andrej730 2e3cc8e73a Added some MEP related type templates
3 more new types for distribution segments.
Distribution segments work the same way as beams - they come without representation but with IfcMaterialProfileSet defined with some profile. Occurences will also have ports at the start and the end of the segment (ports can be displayed in "Ports" section in "Services and Systems"). If you extend the segment or move it around - ports will move accordingly.

Small demo - https://imgur.com/a/Aq0utXa
2023-07-20 18:08:23 +05:00
Andrej730 6b276b8aed Small type manager refactor and more descriptions 2023-07-20 18:08:23 +05:00
Dion Moult 1b71f4f569 Implement guid facet in facet selector 2023-07-20 19:42:34 +10:00
Dion Moult 452089596e New facet based selector syntax. Proposed to supersede existing selector syntax. 2023-07-20 19:22:15 +10:00
Dion Moult a360bac4c3 You can now explicitly create new IfcTypeProducts. 2023-07-20 19:14:24 +10:00
Gorgious56 09bc05f1af Fix #3480 : Scene tabs now show their individual description on hover
Also correct minor typo
2023-07-20 10:42:11 +02:00
Andrej730 efc84606c5 Support looped railings #3415
Example - https://imgur.com/a/RaLORbk
2023-07-19 16:53:39 +05:00
Dion Moult 268039fb13 Write docs describing how to create profile representations 2023-07-19 19:58:19 +10:00
Dion Moult c59757c61a Fix #3469. Minor regression in linking refactor. 2023-07-19 19:58:19 +10:00
ay-ex 0e3386438e fix error messages 2023-07-19 18:39:13 +10:00
ay-ex 38782f4f53 fix choco_regex
fixes new regex needed for upstream blender cmake adjustment, 
pulls all regex and url into contant variables, 
improves error message, when a version cannot be retrieved
2023-07-19 18:39:13 +10:00
Dion Moult da23e39084 Merge pull request #3467 from c4rlosdias/v0.7.0
[BlenderBIM] Add UI to IfcTester
2023-07-19 09:46:39 +10:00
Gorgious 88278a9edb Boolean subpanel is now located in the Geometric Relationships panel 2023-07-18 21:45:48 +02:00
Carlos Dias a23a66edc2 Update __init__.py 2023-07-18 15:28:37 -03:00
Carlos Dias d2c22b0b0c Update handler.py 2023-07-18 15:27:30 -03:00
Gorgious56 76a4850238 Show ifc modifiers only when applicable to current element 2023-07-18 19:31:51 +02:00
Gorgious56 bf5c8f5e88 Disable some tab buttons when no ifc file is loaded 2023-07-18 18:54:24 +02:00
c4rlosdias 36b45b744d add export bcf to ifctester (2023_07_18) 2023-07-18 13:35:43 -03:00
Andrej730 bffbfb5fa3 Small description for some type templates 2023-07-18 19:52:38 +05:00
Gorgious56 963ffa39b7 Housekeeping - Reorder suggestion dictionary keys alphabetically 2023-07-18 16:17:51 +02:00
Carlos Dias 25d76152f6 Merge branch 'IfcOpenShell:v0.7.0' into v0.7.0 2023-07-18 09:22:32 -03:00
c4rlosdias d851614cbb Add bug fixes 2023-07-18 09:21:47 -03:00
Gorgious56 9c7b073414 Refactor edit mode toggle logic for profile based representations 2023-07-18 14:02:56 +02:00
Gorgious56 66ed95fbc1 Add ifc modifier utilities to tool.Blender 2023-07-18 14:00:30 +02:00
Andrej730 44e98b17a0 Fixing railing/roof bug #3416
Issue occured when you imported .ifc and then would try to edit roof/railing path and then cancel/finish editing.

Added safe check to avoid using `change_object_data` in edit mode - https://projects.blender.org/blender/blender/issues/110232
2023-07-18 15:52:59 +05:00
Gorgious56 a095d93838 Fix bug when using the search operator (F3) with no active object 2023-07-18 10:43:36 +02:00
Dion Moult 892b41a540 New geometry.create_2pt_wall API function to create a wall-like representation from two points. 2023-07-18 18:29:26 +10:00
Dion Moult 0cdce9b2d6 Add documentation for basic geometry creation 2023-07-18 18:28:46 +10:00
Dion Moult 05e05d0d5a Rename sverchok representation to mesh representation 2023-07-18 18:22:56 +10:00
Dion Moult 8cbe298072 Utility function to create rotation matrixes in numpy. 2023-07-18 14:28:35 +10:00
c4rlosdias 7bc23c996b Add select entities (2023_07_17) 2023-07-17 17:07:45 -03:00
Gorgious56 b9ff4f3263 Fix Ifc modifier error after #3393064 because of legacy context.object
For some reason in the scene properties context.object returns None even if an object is active  in the viewport, so ifc modifiers would randomly throw errors. AFAIK functionally they are supposed to point to the same thing, but active_object seems to be the superior one and context.object deprecated and kept for legacy reasons. Couldn't find dev quote but I'm sure I saw it at some point
2023-07-17 14:48:01 +02:00
Gorgious56 42bbc9a093 Ensure positive extrusion depth
This prevents an error and mesh corruption when trying to input a negative extrusion depth
2023-07-17 14:29:35 +02:00
Dion Moult 33930642a4 Move all modifier panels into Representations and Materials tab. 2023-07-17 22:21:31 +10:00
ay-ex 0573ffb11a fix choco_update action
fix according to upstream makefile change: 
https://github.com/blender/blender/blob/main/build_files/cmake/Modules/FindPythonLibsUnix.cmake
2023-07-17 22:16:55 +10:00
Andrej730 21a614560a Revision Clouds - option to match revision cloud with the selected object
Works similarly to tagging, example - https://imgur.com/a/Atr0nYE

This feature also can be useful in the future if we integrate revision clouds with ifcgit
2023-07-17 16:32:40 +05:00
Dion Moult 80d74866a9 More tab and panel shuffling. Top level panels now have a tab prefix. 2023-07-17 18:17:32 +10:00
Dion Moult d75e85b3a6 Disable Shift-D duplication of drawings for now until drawings stabilise more. Use the dedicated duplicate drawings feature. 2023-07-17 18:17:32 +10:00
Andrej730 a101f46478 Fixing bugs on small revision clouds #3310
Bugs were:
1) svg print ZeroDivisionError if there was less than 1 pattern segment
2) svg drawing wasn't preserving edge direction if there was less than 1 pattern segment
2023-07-17 12:11:52 +05:00
Andrej730 2463b77985 Fixed #3441 2023-07-17 11:32:12 +05:00
Dion Moult e695bdbd0d Fix #3449. Fix bug where you couldn't edit the relating type of an annotation. 2023-07-17 15:47:10 +10:00
Dion Moult 17230e4170 Fix bug where loaded point geometry had unlinked meshes or placements with wrong units. 2023-07-17 15:46:33 +10:00
Dion Moult 80d88b3821 Fix #3275. You can now rename annotation types from the annotation manager. 2023-07-17 14:32:05 +10:00
Dion Moult 0500e666bd Add support for arbitrary point annotations and survey point, traverse points, control points, spot elevations, etc. 2023-07-17 11:48:09 +10:00
c4rlosdias 31bb220d38 Update 2023_07_16 2023-07-16 20:46:26 -03:00
Dion Moult 944f2f701a Fix #3288. Implement undo / redo until specified named transaction. Fix fundamental undo bug where Blender doesn't track the undo UUID property if you toggle edit mode.
This also has the nice side effect of implementing "Jump to point in history" so you can now use the Edit > Undo History menu.
2023-07-16 22:58:56 +10:00
Dion Moult 844cc62960 More panel shuffling, new Quality and Coordination tab. Geometry is now part of materials. 2023-07-15 21:47:35 +10:00
Dion Moult 147df7f46b Bulk rename panels to remove redundant IFC prefix 2023-07-15 21:02:40 +10:00
Dion Moult 53bd4bf5ea Example of UI if we sentence-cased IFC classes. Good or bad? Please comment at https://community.osarch.org/discussion/comment/16493 2023-07-15 21:02:40 +10:00
Andrej730 d50e43197e small fix 90fce1804 2023-07-15 10:28:23 +05:00
Dion Moult 90fce18042 New icon tabbed interface so you don't need to click twice to change tab. 2023-07-15 13:22:13 +10:00
c4rlosdias 772414451e update ifctester - 2023_07_14 2023-07-14 12:04:22 -03:00
Dion Moult 0a840188bf DANGER! Potentially fix a bunch of undo bugs by completely rebuilding undo id_map tracking with a much simpler solution.
In the past, we used undo_pre and redo_pre and a bunch of complex commit_link and rollback_link tracking to figure out what IDs and GUIDs were added / removed during transactions and how to handle invalidated objects in the id/guid maps.

I really don't know why I made it that complex.

Now we simply treat all data as bad and rebuild the either map. This means slower undos / redos for large projects, but in theory the maps will never invalidate.

This either fixes a lot of things or breaks a lot of things :) Watch out!
2023-07-15 00:05:34 +10:00
Dion Moult a2e7cb8158 Fix undo bugs in the type module where some operators were not part of the undo system for some reason. 2023-07-15 00:00:10 +10:00
Dion Moult 2034fa203b Fix #3275. You can now rename types from the type manager. 2023-07-14 23:58:33 +10:00
Andrej730 572a8c812e root.remove_product fix 2023-07-14 18:22:31 +05:00
Andrej730 af4b5e1fe6 oops, forgot to clean up the test name 2023-07-14 18:02:51 +05:00
Andrej730 9c383e0c1a Fixed plan level annotation rotation #3440 2023-07-14 17:42:45 +05:00
Andrej730 a72231cb91 StairData refactor, solves #3416 2023-07-14 16:17:14 +05:00
Andrej730 846dbc341d root.copy_class to support copying ports in ifc2x3 2023-07-14 15:48:41 +05:00
Andrej730 ed95516b9d Clean up type objects on ifc import 2023-07-14 15:04:15 +05:00
Andrej730 b6ddc0d55e avoid IfcSpatialElement errors in ifc2x3
IfcSpatialElement was added only in ifc4
2023-07-14 14:46:58 +05:00
Andrej730 be5950bcb7 clean up after running test_wall_opening.py 2023-07-14 14:46:58 +05:00
Andrej730 6810bbff2f remove_product to remove IfcRelConnectsElements 2023-07-14 14:46:58 +05:00
Andrej730 2fa7cbaabf Fixing confusing UI #3357
It was showing "Edit Profile" when you have slab and wall selected when it will actually extend the wall to the slab.
2023-07-14 14:46:58 +05:00
Dion Moult 69fa12423d See #3421. Fix drawing bug where IfcOpenShell might return unclosed paths even though the shape is closed and therefore break cut polygon fills. 2023-07-14 17:32:45 +10:00
Dion Moult 8f609ff383 See #3421. Fix bug where some projection fills wouldn't polygonize correctly in shapely due to numerical tolerance. 2023-07-14 17:29:30 +10:00
Dion Moult 924bf2a3ad Fix #3442. Update sheet list if drawing name changes. 2023-07-14 13:54:24 +10:00
Dion Moult 2be8f4a139 Move object data out of object tab and into new object dropdown. 2023-07-14 13:41:02 +10:00
Dion Moult 8411feedf4 You can now open projects from the Ctrl-N shortcut. 2023-07-14 13:40:09 +10:00
Gorgious56 56e6c8e0e1 Improve slightly the boolean interface in mesh properties editor 2023-07-13 14:52:00 +02:00
Dion Moult 818226d1e2 Fix #3414. The project wizard now doesn't load a fresh session by default. Note that File > Open still enforces a fresh session. 2023-07-13 22:34:13 +10:00
Andrej730 6336241431 Cleaning up connections on delete_ifc
Noticed an error if you connect wall and slab, then remove the slab and try to regenerate the wall - error occured because connection to that slab wasn't removed.

The error was:
Error: Python: Traceback (most recent call last):
  File "\3.6\scripts\addons\blenderbim\bim\module\model\workspace.py", line 381, in invoke
    return self.execute(context)
  File "\3.6\scripts\addons\blenderbim\tool\ifc.py", line 136, in execute
    IfcStore.execute_ifc_operator(self, context)
  File "\3.6\scripts\addons\blenderbim\bim\ifc.py", line 421, in execute_ifc_operator
    result = getattr(operator, "_execute")(context)
  File "\3.6\scripts\addons\blenderbim\bim\module\model\workspace.py", line 373, in _execute
    getattr(self, f"hotkey_{self.hotkey}")()
  File "\3.6\scripts\addons\blenderbim\bim\module\model\workspace.py", line 465, in hotkey_S_E
    bpy.ops.bim.join_wall(join_type="T")
  File "C:\software\Steam\steamapps\common\Blender\3.6\scripts\modules\bpy\ops.py", line 113, in __call__
    ret = _op_call(self.idname_py(), None, kw)
RuntimeError: Error: Python: Traceback (most recent call last):
  File "\3.6\scripts\addons\blenderbim\tool\ifc.py", line 136, in execute
    IfcStore.execute_ifc_operator(self, context)
  File "\3.6\scripts\addons\blenderbim\bim\ifc.py", line 421, in execute_ifc_operator
    result = getattr(operator, "_execute")(context)
  File "\3.6\scripts\addons\blenderbim\bim\module\model\wall.py", line 76, in _execute
    joiner.join_E(context.active_object, context.scene.cursor.location)
  File "\3.6\scripts\addons\blenderbim\bim\module\model\wall.py", line 1038, in join_E
    self.recreate_wall(element1, wall1, axis, body)
  File "\3.6\scripts\addons\blenderbim\bim\module\model\wall.py", line 1145, in recreate_wall
    height = self.clip(obj, tool.Ifc.get_object(rel.RelatingElement))
  File "\3.6\scripts\addons\blenderbim\tool\ifc.py", line 89, in get_object
    return IfcStore.get_element(element.id())
AttributeError: 'NoneType' object has no attribute 'id'
2023-07-13 17:32:25 +05:00
Dion Moult 7720166c31 Fix #3393. Bug where duplicating a typed text didn't also copy its literal. 2023-07-13 22:22:00 +10:00
Carlos Dias f5ba0673e3 Merge branch 'IfcOpenShell:v0.7.0' into v0.7.0 2023-07-13 08:01:54 -03:00
Andrej730 3b05799956 Delete edit text from BIM Tool since it's already in Annotation Tool 2023-07-13 15:54:28 +05:00
Andrej730 f3924249e9 handle null diffuse color loading ifc styles
The error was:
Traceback (most recent call last):
  File "\3.6\scripts\addons\blenderbim\bim\module\style\prop.py", line 99, in update_shading_style
    tool.Style.set_surface_style_props(blender_material)
  File "\3.6\scripts\addons\blenderbim\tool\style.py", line 174, in set_surface_style_props
    style_data["DiffuseColour"] = style_data["DiffuseColour"][1]
TypeError: 'NoneType' object is not subscriptable
2023-07-13 14:54:36 +05:00
Gorgious56 7f9e8a207d Enable drawing generation buttons only when an actual drawing is selected 2023-07-13 11:38:11 +02:00
Andrej730 38f7c044a3 Fixed error on canceling editing railing/roof
Since we do not update representation for railing / roofs and just mark them wtih `tool.Ifc.edit(obj)` their previous representation may still be incorrect. So we restore the previous representation just using bmesh update method.

For example if create a new roof, enter editing roof properties mode and then cancel editing, then it would result in "Failed to process a shape" because roof still had just empty Body/PointCloud representation.
2023-07-13 14:32:09 +05:00
Andrej730 7896d5b6e5 Fixing errors on loading ifc after ef8f82501
The error was:
RuntimeError: Operator bpy.ops.object.editmode_toggle.poll() failed, context is incorrect
Location: C:\software\Steam\steamapps\common\Blender\3.6\scripts\modules\bpy\ops.py:113
Traceback (most recent call last):
  File "\3.6\scripts\addons\blenderbim\bim\module\project\operator.py", line 594, in execute
    bpy.ops.bim.load_project_elements()
  File "C:\software\Steam\steamapps\common\Blender\3.6\scripts\modules\bpy\ops.py", line 113, in __call__
    ret = _op_call(self.idname_py(), None, kw)
RuntimeError: Error: Python: Traceback (most recent call last):
  File "\3.6\scripts\addons\blenderbim\bim\module\project\operator.py", line 653, in execute
    ifc_importer.execute()
  File "\3.6\scripts\addons\blenderbim\bim\import_ifc.py", line 265, in execute
    self.clean_mesh()
  File "\3.6\scripts\addons\blenderbim\bim\import_ifc.py", line 1347, in clean_mesh
    bpy.ops.object.editmode_toggle(context_override)
  File "C:\software\Steam\steamapps\common\Blender\3.6\scripts\modules\bpy\ops.py", line 111, in __call__
    ret = _op_call(self.idname_py(), C_dict, kw, C_exec, C_undo)
RuntimeError: Operator bpy.ops.object.editmode_toggle.poll() failed, context is incorrect
Location: C:\software\Steam\steamapps\common\Blender\3.6\scripts\modules\bpy\ops.py:113
2023-07-13 13:45:50 +05:00
Andrej730 96d812eaba Fixed console errors on checking modeling data 2023-07-13 13:03:18 +05:00
Dion Moult 0390a65590 Fix #3300. Bug where it was impossible to delete normal Blender objects in the outliner when an IFC existed.
Also port over batch deletion to the outliner override.
2023-07-13 16:26:06 +10:00
Andrej730 ad4ab1567f model data parameters renamed to pset_data for consitency 2023-07-13 11:08:52 +05:00
Andrej730 d633733d18 DoorData use refactor 2023-07-13 10:58:03 +05:00
Andrej730 cd1a813630 WindowData use refactor 2023-07-13 10:36:09 +05:00
Andrej730 c9e6da9b6c change .is_editing type for ifc modifiers to boolean 2023-07-13 09:53:11 +05:00
Andrej730 cada2c36e3 RoofData use refactor 2023-07-13 09:48:33 +05:00
Dion Moult 33304eb1a5 Group drawings by target view in the drawing list 2023-07-13 14:13:28 +10:00
Bruno Perdigão 4e72ef8b20 Select all objects in array modifier #3331 2023-07-13 13:21:49 +10:00
Dion Moult b9d8e86748 Fix #3411. Filtering sheets now doesn't break sheet hierarchy and also lets you search for sheet identification, name, and names of drawings / titleblocks / things on sheets. 2023-07-13 11:53:08 +10:00
Dion Moult 3adfc63b65 Fix #3432. Bug where null applicable occurrence of a type product caused the annotation tool to fail. 2023-07-13 11:27:01 +10:00
Andrej730 91e7d49060 makefile fix 2023-07-12 18:34:48 +05:00
Andrej730 4ad1a2be73 minor change for RailingData 2023-07-12 17:46:56 +05:00
Andrej730 ef8f825012 bim.load_project to refresh session on execute not invoke
if you accidentally opened file dialog or just changed your mind it shouldn't remove all objects from the current blender session
2023-07-12 17:10:30 +05:00
Andrej730 56adfc70b5 RailingData use refactor, solves #3416 2023-07-12 17:01:51 +05:00
smr02 bc909a26fa ifcsverchok delete api_WIP 2023-07-12 21:18:20 +10:00
Andrej730 dd0a015ec3 display current commit in status bar
it works for users who symlinked their blenderbim/bim folders to git repository or installed blenderbim from release created from makefile
https://i.imgur.com/mEBH9I7.png
2023-07-12 12:34:24 +05:00
Gorgious56 3813c26db7 Fix error when trying to edit an object that doesn't support edit mode with the TAB keypress
Also run black on the rest of the file
2023-07-12 09:28:21 +02:00
Dion Moult 95c06c7f82 Fix #3420. Fix missing sheet references on drawings and incorrect direction of section references. 2023-07-12 17:17:54 +10:00
Andrej730 db910d4024 more consistency in values for plan-, section level and diameter 2023-07-12 11:47:19 +05:00
Andrej730 669dbd2e8d radius annotation - support for prefix/suffix/fill-bg
and more consistency between viewport and svg
2023-07-12 11:47:18 +05:00
Andrej730 becbfccae9 PLAN/SECTION_LEVEL consistency between viewport and svg
before - https://i.imgur.com/bwYbozz.png
after - https://i.imgur.com/7BlmQIg.png

NOTE that if you're using markers.svg that was created before this update you won't see the changes for the section-/plan-level markers.
2023-07-12 11:47:18 +05:00
Andrej730 2f02d401f2 fill-bg for diameter, plan- and section-level annotations 2023-07-12 11:47:18 +05:00
Dion Moult ec2ca4bcf4 Fix #3417. Apply 50mm offset to camera to ensure text isn't "behind" the camera. This 50mm convention is used in quite a few places, we should standardise it. 2023-07-12 12:37:53 +10:00
Dion Moult 89df937f89 Fix #3425. Fix invalid IFC data where grid axes should be 2D not 3D. 2023-07-12 11:41:58 +10:00
Thomas Krijnen 7567971a32 Update entity_instance.py 2023-07-11 21:41:06 +02:00
Thomas Krijnen 74160731c9 Update rule_executor.py 2023-07-11 21:40:05 +02:00
Andrej730 5d7fefa042 Prevent resetting selected tab on blender restart
Not sure if there is any caveats to that
2023-07-11 18:23:26 +05:00
Dion Moult db4c61a472 Fix #3407. Override Ctrl-S to the export IFC which checks for Blender projects.
1. No IFC --> Saves Blend file
 2. IFC, unsaved Blend --> Saves IFC
 3. IFC, saved Blend --> Saves IFC and Blend
2023-07-11 23:08:05 +10:00
Andrej730 298568d3ea PLAN_LEVEL annotations support for prefix, suffix #3399
Viewport - https://i.imgur.com/XRa3oJO.png
SVG - https://i.imgur.com/C8PjoiO.png
2023-07-11 17:04:58 +05:00
Dion Moult 4da7b355bf Fix #3414. Only clear default scene, not custom scene when loading / creating IFCs. 2023-07-11 21:45:34 +10:00
Andrej730 2a15743f8b SECTION_LEVEL annotations support for prefix, suffix #3399
also added support for "show only description" for section_level
2023-07-11 16:42:07 +05:00
Andrej730 e54cc11ca6 Fixed #3400 introduced in dda16c8 2023-07-11 14:54:51 +05:00
Dion Moult 24fdfc6d82 Resolve merge conflict 2023-07-11 18:15:18 +10:00
Dion Moult 8689164769 Fix registration bug that created hundreds of workspaces due to continuous triggering of the depsgraph_update.
Use an is_registering check to only run the on_register once.

Rename setDefaultProperties to load_post to better reflect what handler it is.

Unregister our custom scene panels upon addon activation.

Move aprops getting to tool.Blender because it is a bit complex. Also, aprops may not be setup until load_post is called, so the len() check protects against that.
2023-07-11 18:08:02 +10:00
Gorgious56 86dd79daa6 Keep active object when unlinking an IFC element
Unlinking an element recreates a new blender object so the active object would be lost, jumping the properties to the scene panel which was slightly annoying
2023-07-11 09:33:06 +02:00
Andrej730 a231cc31e8 fixed properties search after 949a2bb and fixed console errors on search
1) When you search not from the SCENE properties blender creates some kind of phantom area to check search results. As this area is not present in `context.screen.areas` it was resulting in the error below.

2) After 949a2bb search in scene properties was limited to currently selected tab. Given the amount of tools we have in properties and native blender approach for searching across all available sections in properties, it seems reasonable to make search work across all tabs.

Console error was:
The error was
Python: Traceback (most recent call last):
  File "\3.6\scripts\addons\blenderbim\bim\ui.py", line 281, in draw
    aprops = context.screen.BIMAreaProperties[context.screen.areas[:].index(context.area)]
ValueError: bpy.data.screens['Layout.001']...Area is not in list
2023-07-11 11:00:20 +05:00
Dion Moult e412d27398 Integrate brickschema undo system into BlenderBIM Add-on undo system wrapper 2023-07-11 11:16:49 +10:00
Andrej730 60388f1a24 Fixed #3412
After 949a2bb printing happens not fron N-panel (which is part of viewport) but from scene properties - so current context doesn't have region_3d anymore.

PS The same issue was there all along if you'd try to print the drawing from Camera Properties tab.
2023-07-10 17:31:11 +05:00
Andrej730 d13d1493e3 allow to call select/color by attribute by providing arguments
instead of always checking scene.BIMSearchProperties. This allows to add different setups of selections to quick favorites
2023-07-10 17:17:12 +05:00
Dion Moult 556489d686 Implement Ctrl-Tab in properties to conveniently toggle between two tabs. 2023-07-10 22:04:10 +10:00
Dion Moult 657e77117c Move built-in scene panels to a dedicated "Blender Properties" tab instead of unregistering permanently. 2023-07-10 22:04:10 +10:00
Andrej730 7d97f5e375 Library for US AISC15 Steel Profiles #3203 2023-07-10 16:36:07 +05:00
Gorgious56 7583b56738 Trying to edit a railing modifier path while modifying the parameters is now prevented and user is informed with a tooltip 2023-07-10 13:00:43 +02:00
Gorgious56 073a00b2ca Fix IfcRailing modifier support spacing <= 0 would throw an error, and deactivate support spacing field if manual support is selected 2023-07-10 12:47:46 +02:00
Thomas Krijnen 8baa0945e9 2dc4bbf8cd Apply same logic for derived attributes 2023-07-10 10:26:37 +02:00
Dion Moult 717c28b7c9 Override Ctrl-N new file menu to make it easier to start new projects and new IFC icon. 2023-07-10 17:16:32 +10:00
Andrej730 f4a4e906ab Using EPset_Drawing data instead of camera props on drawing print #3396
Previously if you change `HasUnderlay` in pset it caused errors because camera properties `has_underlay` wasn't updated.
Still some work need to be done for updating keep camera properties in sync with pset - probably we can should call something like `tool.Drawing.import_drawing()` on EPset_Drawing edit
2023-07-10 11:09:48 +05:00
Dion Moult 052ff71c51 See #3411. Sheets are now sorted alphabetically by default. 2023-07-10 14:13:44 +10:00
Dion Moult 3dfd36877e See #3016. Scene shading styles are now stored as part of the drawing style. 2023-07-10 14:13:44 +10:00
Dion Moult a9f2237d32 Update splash image to be centered 2023-07-10 13:15:49 +10:00
Dion Moult 62c93f0462 Fix #3410. Minor regression when moving UI to properties panel. 2023-07-10 11:19:15 +10:00
Dion Moult 60ea504267 Add support for Point representations too, not just point cloud. 2023-07-10 10:23:33 +10:00
Dion Moult 1e3b45c0b8 Fix bug where all grouped annotations were excluded when opening models, whereas we only want drawing annotations to be excluded. 2023-07-10 10:23:33 +10:00
Massimo Fabbro 757277cedf Fix bug where drawing decimal places couldn't be 0. Now it's possible to define 0 as value 2023-07-09 18:52:03 +02:00
Dion Moult 14fced7aba Forgot to commit updated IfcTester test suite 2023-07-09 22:30:14 +10:00
Dion Moult 7a3b299148 Fix critical undo sync bug related to invoking instead of executing. See notes.
Currently, the undo system works by wrapping the execute operator. What was not considered was that execute() isn't the only entry point to operators. Operators can be invoked. If an invoked operator calls a bpy.ops.bim.something in its invoke() function, then bpy.ops.bim.something will start and finish its own transaction, and then when the invoke finishes and finally runs execute(), another transaction will be started and finished. This means that we get at least one extra transaction logged (one per any call to bpy.ops.bim.foo in invoke()).

This mismatch of number of transactions and undo stack means that when the user presses ctrl-z, the undo doesn't actually occur. This leads to a sync bug.

To be safe, all IFC invokes must be wrapped. This will take some time to review so expect another commit soon.
2023-07-09 22:29:49 +10:00
Dion Moult 45cf6a8ace Drawing panels now moved to the drawings and documents "tab" 2023-07-09 18:30:40 +10:00
Dion Moult 9b1693e112 Update IfcTester to comply with latest modifications to IDS XSD. 2023-07-09 17:02:12 +10:00
Dion Moult cc59929cfd Visual clean up of annotation tool to only activate when a drawing is active and have a type manager similar to the BIM tool. 2023-07-09 16:01:16 +10:00
Dion Moult 43fd012a4c Update tool icons to all use the same underlying "cube" shape and add bracing for structural icon and background walls for spatial icon. 2023-07-08 22:56:23 +10:00
Bruno Perdigão f96f663dc3 recreate decompositions in Refresh operator 2023-07-08 22:12:24 +10:00
Bruno Perdigão 486bafc814 changed keymap 2023-07-08 22:12:24 +10:00
Bruno Perdigão 20a31be5bf clear BIMObjectPropertis.collection after duplication 2023-07-08 22:12:24 +10:00
Bruno Perdigão 98dad0cbbe initial commit 2023-07-08 22:12:24 +10:00
Dion Moult b0ce2e5a00 Fix #3390. Type products should not be set in the model interface. 2023-07-08 22:09:50 +10:00
Thomas Krijnen 3aea0915d0 #3189 proposal (#3190) 2023-07-08 20:07:28 +10:00
Bruno Perdigão 0ee5e3887b removed total_items from the operator 2023-07-08 20:06:18 +10:00
Bruno Perdigão 1afcec2005 implementation of #3332 2023-07-08 20:06:18 +10:00
Dion Moult 656b7cb06e Merge pull request #3405 from brunoperdigao/pset_select_same_value
Bug fix in #3392
2023-07-08 20:05:22 +10:00
Dion Moult 19d3f98b6f Merge branch 'v0.7.0' into pset_select_same_value 2023-07-08 20:05:13 +10:00
Dion Moult 73a736f52a Fix #3402. Activate drawing no longer affects the visibility of non-IFC objects. 2023-07-08 19:55:45 +10:00
Dion Moult 6adc48ade4 Fix #3403. Templates must be specific to the version you are creating. 2023-07-08 19:30:11 +10:00
Dion Moult 949a2bb347 Start mocking up our own property "tabs" with the intention to use this new tab system instead of Blender property tabs
Blender property tabs are hardcoded and we cannot customise them. IFC data only "somewhat" maps to Blender property tabs, and some things fail to map causing user confusion. For example material and styles are very confusing. Also scene and object properties are crazy overpopulated (see how many times IFCArchitect bulk collapses things). And this also causes things to be spread out and lost (e.g. some random panels in viewport N menu). This should give us a lot more control to organise panels exactly how we want them.
2023-07-08 17:03:06 +10:00
Dion Moult 8cb63720eb Workspace inspired by IFCArchitect's layout is now setup by default and irrelevant Blender scene panels are hidden. You can disable this behaviour for advanced users, or use your own workspace layout but at least now there is a better "BIM-optimised" layout.
The layout uses three columns with a focus on a larger outliner (important as BIM models have lots of objects and complex hierarchies!) and properties, and removes the mostly irrelevant timeline for beginners.
2023-07-08 16:26:21 +10:00
Dion Moult 52ac1090e9 You can now create, open, and save projects from the file menu, with simple project presets. Also the scene is cleared so no more manual cube / light / camera deletion. Also poll prior to saving. No more import / export menu to confuse users.
https://imgur.com/a/EIt2H5b
2023-07-08 14:39:39 +10:00
Dion Moult 4f6180baf8 Delete old annotation panel in favour of annotation tool 2023-07-08 10:58:53 +10:00
Bruno Perdigão 743339598e minor fix 2023-07-07 15:27:48 -03:00
Bruno Perdigão 2e423f26ff bugfix and alt+click functionality 2023-07-07 15:19:35 -03:00
luzpaz b3b45893cc Fix various typos in src/ifcgeom* subdirs 2023-07-07 18:44:02 +02:00
Dion Moult a65747dd2c Minor doc improvements and SQL/stream code cleanup 2023-07-07 18:12:52 +10:00
Dion Moult 4286ad6dcc Fix #3394. Fix bug where railing and roof type names were hardcoded. Also improve UI of create type form so you can actually read the fields. 2023-07-07 17:57:09 +10:00
Dion Moult 0b823c772e Fix #3213. Internally, we should always use Unix path separators. 2023-07-07 17:20:07 +10:00
Dion Moult 9944024457 The user can now toggle between rotate before / after translating in OffsetObjectPlacements 2023-07-07 15:57:42 +10:00
Dion Moult 3cdad90755 Fix #3016. Drawing styles that set the shading style will change shading style upon activating the style. 2023-07-07 15:17:53 +10:00
Dion Moult 71846d683b Fix #3121. Merge equivalent contexts when merging projects in MergeProject Ifcpatch recipe. 2023-07-07 13:04:44 +10:00
Dion Moult 7be0fd6137 Fix failing tests since Github test runner only has IFC4X3_ADD1 not TC1 compiled in. 2023-07-07 12:33:38 +10:00
Bruno Perdigão 1eef746f60 implements #3268 2023-07-07 12:15:05 +10:00
Trashman247 1696ca5e08 Remove binding namespace new project (already in schema) 2023-07-06 16:47:53 -07:00
Trashman247 c011458ea2 remove import BrickStore in props 2023-07-06 16:47:53 -07:00
Trashman247 abf809f9dd Add brick operator accepts label parameter & add toggle for namespace editing 2023-07-06 16:47:53 -07:00
Trashman247 9257816e62 Add filter to listed namespaces (need add more aliases) 2023-07-06 16:47:53 -07:00
Trashman247 d47125b28f Double space core functions and clear brick browser on project close 2023-07-06 16:47:53 -07:00
Trashman247 b7453f584e Separate serialize into "save" and "save as" with file selection 2023-07-06 16:47:53 -07:00
Bruno Perdigão 6a7cf6843a implements #3268 2023-07-06 15:10:53 -03:00
Sigma Dimensions 9c4fa666c9 Georeferencing utility to convert an angle in degrees to X Axis Abscissa and Ordinate 2023-07-06 16:02:03 +01:00
Dion Moult a970120fac See #3126 and 5b4010c. Fix failing tests, and allow schema_by_name to follow the same schema_version distinction as file. 2023-07-06 22:40:31 +10:00
Andrej730 72907cd3f0 w-type US steel profiles #3203 2023-07-06 17:09:03 +05:00
Dion Moult ae7c9f35a6 See #3126. Forgot to commit tests related to explicit schema versions. 2023-07-06 20:56:33 +10:00
Thomas Krijnen 0b111084b0 Update win32 download links installation.rst 2023-07-06 11:30:09 +02:00
Thomas Krijnen 2ee0dcaf64 #3359 return early in case of no-prefiltering 2023-07-06 11:21:17 +02:00
Andrej730 70eabead92 fill-bg support for dimensions #3387
Example - https://i.imgur.com/lvJwtDI.png
works the same way as for text by adding "fill-bg" to EPset_Annotation.Classes
2023-07-06 13:52:51 +05:00
Dion Moult 5a30d9fb9b Fix #3383. Disallow any extrusion angles >=90 which are invalid. 2023-07-06 17:34:22 +10:00
Dion Moult 3126db342d Test all shapes in the debug panel now also attempts 2D checks to prevent false positives. 2023-07-06 17:24:51 +10:00
Dion Moult c0db03438f Revert "More descriptive RuntimeError: Failed to process shape #3383"
This reverts commit 1542217117.
2023-07-06 17:02:57 +10:00
Andrej730 1542217117 More descriptive RuntimeError: Failed to process shape #3383
Example of the new error - https://i.imgur.com/0FMQw5V.png
2023-07-06 10:29:17 +05:00
Dion Moult 5b4010c67d Fix #3126. Implement mapping from IFC4X3 to IFC4X3_ADD1 and explicit version identifiers in ifcopenshell.file 2023-07-06 14:22:36 +10:00
Dion Moult c18d96f0e3 Fix #3386. 2023-07-06 13:46:09 +10:00
Dion Moult f9173700a6 Fix #3349. Allow inserting a filling into an uncontained building element. 2023-07-06 13:40:29 +10:00
Bruno Perdigão 7100a7fa8e fix #3063 2023-07-06 12:39:20 +10:00
Dion Moult 0078d691d3 Fix #3382. Minor regression in wall join refactor from d64c135 2023-07-06 11:45:12 +10:00
Sigma Dimensions fd626e37a0 fix #3385 - renaming grids with "F2" 2023-07-05 17:41:57 +01:00
Andrej730 e62d7ad9ef Moving editing roof and railing paths hotkey from Shift+E/Q to Tab 2023-07-05 18:40:35 +05:00
Andrej730 80eebc03d3 Fix #3263 2023-07-05 18:04:14 +05:00
Andrej730 62fea990d9 railing modifiers - fixing normals for frameless_panel #3277 2023-07-05 17:47:16 +05:00
Dion Moult 7ab01e6ac0 See #3359. Bump IfcOpenShell and enable HLR prefiltering optimisation for drawing generation 2023-07-05 22:18:53 +10:00
Andrej730 74a025b81c type selection error #3318
if you switch ifc_class then relating_type_id can become invalid and we wasn't taking it into account

Also fixed bunch of warning like `pyrna_enum_to_py: current value '17' matches no enum in 'BIMModelProperties', '', 'relating_type_id'`` when some ifc_class has N related types and then you switch to other ifc_class that has less than N related types.
2023-07-05 16:52:23 +05:00
Andrej730 ca8ed87f2a Revert "enable activating object type when trying to select type from an object ( whilst the type is hidden from view layer)"
This reverts commit 4bc987924d since there was some unintentional changes.

Now when you use select_type in object properties - ifc class and related type gets selected in the BIM Tool  - https://i.imgur.com/drIHYDz.png

#3318
2023-07-05 16:52:23 +05:00
Andrej730 9fbcb3900f ShapeBuilder.polyline - skip creating segments if they are not needed 2023-07-05 13:02:17 +05:00
Dion Moult 1533283822 Fix #3356. Include version info in status bar for debugging and new copy debug info button to help bugreporting. 2023-07-05 17:39:43 +10:00
Dion Moult 6beca6382f Fix #3256. Support file profiles made out of multiple representation items. 2023-07-05 14:38:48 +10:00
Dion Moult a1caef5b4f See #3334. Fix some get_pset implementation errors in 64516a1 2023-07-05 10:37:13 +10:00
OrionSehn 7da96b4951 Proposed fix to #3352 2023-07-05 09:56:42 +10:00
Andrej730 66c275f617 Sync ifc class in BIM Tool and Type Manager
It's seems it would be better for ux if they will be connected
2023-07-04 17:17:57 +05:00
Andrej730 52a8fcd86e add_representation - detect non consequtive curves and handle them
Basically if current mesh curve is non consequtive we handle it in similar way to b03c4da7b - convert it to curves using Blender and then save them to ifc.

The advantage is that it creates less curves in ifc because mesh method wasn't able to detect separate curves correctly if vertex order was messed up (usually it is) and it would create separate curve for each curve segment.

I've also replaced figuring vertex duplicates with `bmesh.ops.remove_doubles` manually with `bmesh.ops.remove_doubles` and it's now much faster. In some cases because of the manual calculations it was taking too long - in #3233 Ryan mentiones that it freezes blender, it wasn't really freezing Blender but to finish calculations it would take 10 minutes.
2023-07-04 16:43:42 +05:00
Dion Moult 3ba7ecdb95 See #3376. Bump updated build of IfcSverchok. 2023-07-04 20:17:01 +10:00
Dion Moult 36f7625cee Merge AWS Lambda installation documentation into official docs 2023-07-04 20:10:57 +10:00
Dion Moult ee82714423 Whoops 2023-07-04 19:47:49 +10:00
Dion Moult 281147367e Close #3377. Implement xyz coordinates for COBie object placement. 2023-07-04 19:45:11 +10:00
Dion Moult 0beea4255a Write IfcOpenShell documentation for geometry trees. 2023-07-04 18:04:15 +10:00
Andrej730 b03c4da7bf experimental support for spline curves representation #3233
there is still no full support but as a workaround I've added blender converting curves to meshes and back to curves so we could preserve the actual shapes

before - https://i.imgur.com/9F542Iy.png
after - https://i.imgur.com/t58RlkF.png
2023-07-04 10:57:22 +05:00
Dion Moult c0402687b5 See #3334. Optimise pset getting and add simple "broadphase" filtering for all facets for faster validation. 2023-07-04 15:50:44 +10:00
Dion Moult c6fa7161b6 Fix bug where IfcTester would pass derived attribute facets.
Now that IfcOpenShell supports derived attributes, we need to explicitly make sure the attribute facet only considers forward attributes.
2023-07-04 15:50:44 +10:00
Dion Moult 64516a173a See #3334. Fix bug where get_pset didn't handle property inheritance correctly. 2023-07-04 15:50:44 +10:00
c4rlosdias f507e3e8bd Add extra formats to output file 2023-07-04 12:42:59 +10:00
c4rlosdias 700d8a3b9c Add extra formats to output file 2023-07-03 10:22:08 -03:00
Andrej730 2f15d73b30 Prevent crash on attempt to link the current .blend file 2023-07-03 17:23:49 +05:00
Andrej730 53cc6f0bd6 Storing external styles paths as posix for cross platform compatibility
Also it was wrongly referring to .blend file instead of .ifc to generate relative paths.
2023-07-03 17:09:01 +05:00
Andrej730 7680652c28 disable default vebose mode for getting shader attribute to prevent spam 2023-07-03 16:56:47 +05:00
Carlos Dias da3440d848 Merge branch 'IfcOpenShell:v0.7.0' into v0.7.0 2023-07-03 08:48:10 -03:00
Andrej730 e8b1d00e6d adding default ifc objects added from shift-a menu to the current collection 2023-07-03 16:22:20 +05:00
Andrej730 6ec7e48423 Editiing arbitrary profile without enabling editing profile
It was a bit confusing UX since when you finish editing arbitrary profile path the data is already saved in ifc but you were still in editing profile properties
2023-07-03 15:54:51 +05:00
Andrej730 ccf09b962a Update type manager thumbnails on editing profiles
Example - https://imgur.com/a/IcV8IIR
2023-07-03 14:34:20 +05:00
Andrej730 485d41ae09 disable chaching sound running the bim tests
to save someone's ears (probably mine 😁) from running 10 chaching sounds per second
2023-07-03 12:43:26 +05:00
Dion Moult b67c5a0828 Fix #3366. Fix crash in IfcDiff if you are comparing a model with no geometry. 2023-07-03 17:28:38 +10:00
Andrej730 7b92057227 Fixed errors displaying IFC Connections for IfcRelConnectsElements
IfcRelConnectsElements type of connection wasn't handled properly resulting in errors.
Now you should be able to see connections for connected slabs, walls etc - https://i.imgur.com/LpFBKBC.png

Traceback:
Traceback (most recent call last):
  File "\blenderbim\bim\module\geometry\ui.py", line 102, in draw
    ConnectionsData.load()
  File "\blenderbim\bim\module\geometry\data.py", line 100, in load
    cls.data = {"connections": cls.connections()}
  File "\blenderbim\bim\module\geometry\data.py", line 134, in connections
    relating_element_connection_type = rel.RelatingConnectionType
  File "\blenderbim\libs\site\packages\ifcopenshell\entity_instance.py", line 171, in __getattr__
    raise AttributeError(
AttributeError: entity instance of type 'IFC4.IfcRelConnectsElements' has no attribute 'RelatingConnectionType'
2023-07-03 11:53:03 +05:00
Dion Moult a980ce1bfa Fix #3355. Reimplement graphical clash snapshots in BCF from IfcClash in the BlenderBIM Add-on. 2023-07-03 16:50:40 +10:00
Dion Moult 51b7f24a00 You can now unload BCF projects to load another one. 2023-07-03 16:50:40 +10:00
Dion Moult 136e1c3b3f Merge pull request #3364 from IfcOpenShell/interface-to-interact-with-Brickschema
Interface to interact with brickschema PR #1
2023-07-03 09:55:09 +10:00
Carlos Dias c094517a10 Update operator.py
missing output file format in arguments
2023-07-03 09:42:17 +10:00
Carlos Dias 93aa24c6d2 Update operator.py
missing output file format in arguments
2023-07-02 20:25:51 -03:00
Andrej730 b61a11d5b7 reworked detecting neighbour walls when placing new ones
hopefully it will be a bit more natural now - please give feeback

before - https://imgur.com/a/VYQt5ua
now - https://imgur.com/a/5LRAmrd
2023-07-02 19:59:18 +05:00
Andrej730 012da8fcb9 More responsive UI for IFC search 2023-07-02 15:52:24 +05:00
Andrej730 d64c1358d7 a bit of bim.join_wall refactor 2023-07-02 11:04:00 +05:00
Andrej730 0252701f7a Error editing roof profile #3357
it was assuming it's a slab and was giving errors trying to start editing slab profile

File "C:\Users\user\AppData\Roaming\Blender Foundation\Blender\3.6\scripts\addons\blenderbim\bim\module\model\slab.py", line 618, in _execute
if extrusion.Position:
AttributeError: 'NoneType' object has no attribute 'Position'
2023-07-02 10:44:01 +05:00
Bruno Postle 6fed9bf795 BlenderBIM Git documentation minor corrections 2023-07-01 14:35:32 +01:00
Bruno Postle b088a763d8 BlenderBIM document Git merge asymmetry (#3096) 2023-07-01 09:53:26 +01:00
Dion Moult 104fe56612 Whoops 2023-07-01 15:41:40 +10:00
Dion Moult 9e0760b8d2 Fix hppfcl segfault by bumping hppfcl and its dependencies. Remove support for py37. 2023-07-01 15:37:21 +10:00
Andrej730 f26986645a Added warning for a86456392 2023-06-30 18:19:23 +05:00
Andrej730 a86456392b improved relative path handling for textures #3365
now we're going to store all texture paths in .ifc as posix to keep them working if .ifc moved from one platform to another

previously it was saving all paths as absolute
2023-06-30 18:05:26 +05:00
Andrej730 61de0ad64c Calculating angle based on world position instead of camera pos #3362 2023-06-30 15:02:39 +05:00
Andrej730 243eee9ebf Added support for OCCLUSION textures for 3aff29dec 2023-06-30 14:44:06 +05:00
Andrej730 f23ac034c7 Shape builder - detect AdvancedSweptSolid
To avoid validation errors on creating wall mounted railings

    #143=IfcShapeRepresentation(#15,'Body','SweptSolid',(#116,#126,#129,#139,#142))
Rule IfcShapeRepresentation.CorrectItemsForType:
    IfcShapeRepresentationTypes(self.RepresentationType, self.Items)
Violated by:
    False
     +  where False = IfcShapeRepresentationTypes('SweptSolid', (#116=IfcSweptDiskSolid(#115,10.,$,$,$), #126=IfcExtrudedAreaSolid(#120,#124,#125,20.), #129=IfcSweptDiskSolid(#128,10.,$,$,$), #139=IfcExtrudedAreaSolid(#133,#137,#138,20.), #142=IfcSweptDiskSolid(#141,25.000000372529,$,$,$)))
     +    where 'SweptSolid' = #143=IfcShapeRepresentation(#15,'Body','SweptSolid',(#116,#126,#129,#139,#142)).RepresentationType
     +    and   (#116=IfcSweptDiskSolid(#115,10.,$,$,$), #126=IfcExtrudedAreaSolid(#120,#124,#125,20.), #129=IfcSweptDiskSolid(#128,10.,$,$,$), #139=IfcExtrudedAreaSolid(#133,#137,#138,20.), #142=IfcSweptDiskSolid(#141,25.000000372529,$,$,$)) = #143=IfcShapeRepresentation(#15,'Body','SweptSolid',(#116,#126,#129,#139,#142)).Items
2023-06-30 14:19:07 +05:00
Andrej730 363ede9801 set default position on creating window and door
to avoid validation errors like IfcProduct.PlacementForShapeRepresentation
2023-06-30 14:16:39 +05:00
Andrej730 18d26df24e Fixing ifc git poll errors if .ifc file is not yet saved
Errors:
Traceback (most recent call last):
  File "\Blender\3.6\scripts\addons\blenderbim\bim\module\ifcgit\operator.py", line 20, in poll
    if not os.path.isfile(path_ifc):
  File "\Blender\3.6\python\lib\genericpath.py", line 30, in isfile
    st = os.stat(path)
TypeError: stat: path should be string, bytes, os.PathLike or integer, not NoneType
Traceback (most recent call last):
  File "\Blender\3.6\scripts\addons\blenderbim\bim\module\ifcgit\operator.py", line 48, in poll
    if not os.path.isfile(path_ifc):
  File "\Blender\3.6\python\lib\genericpath.py", line 30, in isfile
    st = os.stat(path)
TypeError: stat: path should be string, bytes, os.PathLike or integer, not NoneType
2023-06-30 13:42:01 +05:00
Andrej730 0df62d5746 updated furniture library after f5031035c and a385938 2023-06-30 13:30:09 +05:00
Andrej730 f5031035c7 Shape builder - avoid duplicated indices in polylines
For example
IfcLineIndex((1,2,3,4,1))
instead of
IfcLineIndex((1,2,2,3,3,4,4,1))
2023-06-30 13:27:00 +05:00
Thomas Krijnen fc50bdd3a0 Fix openbot build 2023-06-30 09:32:10 +02:00
Kristoffer Andersen 3117d5b2e7 Update build.sh
Use only 1 cpu to reduce memory usage for conda builds
2023-06-30 09:27:15 +02:00
Riley Wong e6dc1d1acd Merge branch 'v0.7.0' into interface-to-interact-with-Brickschema 2023-06-30 00:05:20 -07:00
Trashman247 bd8afabce3 Resolve .git-ignore file conflict 2023-06-30 00:03:46 -07:00
Trashman247 01da8498f2 Move Brick.ttl ignore to main git-ignore file 2023-06-29 23:56:20 -07:00
Trashman247 a34b8d06ec Silence known rdflib_sqlalchemy TypeError warning 2023-06-29 23:42:00 -07:00
Massimo Fabbro 5764cfcbb9 Add regen button also under space tool 2023-06-30 14:34:21 +10:00
Dion Moult 1ad2465ad4 See #3283. Minor fix where IfcContext is abstract, so IfcProjectLibrary should be checked instead when loading filtered models. 2023-06-30 13:58:18 +10:00
Dion Moult f28e28abc0 Fix failing build 2023-06-30 11:59:17 +10:00
Dion Moult 97e321dd12 Bundle qhull for hppfcl on Linux too 2023-06-30 11:47:44 +10:00
Thomas Krijnen fc4b36e759 Fix openbot build 2023-06-29 22:04:18 +02:00
Thomas Krijnen 832cdce934 Fix openbot build 2023-06-29 22:03:29 +02:00
Andrej730 e45974df62 Support for door/window/stair modifiers in ifc2x3 2023-06-29 18:38:06 +05:00
Andrej730 bba118ed19 ShapeBuilder to use IfcPolyline of IfcIndexedPolyCurve to support ifc2x3
needed it to add suport for window/door modifiers in ifc2x3 in the next commit
2023-06-29 18:38:06 +05:00
Andrej730 709590401c fixing errors running tests if no tool selected in blender startup file
Error was: AttributeError: 'NoneType' object has no attribute 'idname'
2023-06-29 17:19:40 +05:00
Dion Moult ab74d1456b Bundle qhull for ifcclash on macosm1 2023-06-29 22:10:39 +10:00
Thomas Krijnen 4457da9042 Help swig not to trip over old occt handle definitions 2023-06-29 13:29:49 +02:00
Thomas Krijnen d501118eb4 Add --prefilter flag to draw.py 2023-06-29 13:29:49 +02:00
Thomas Krijnen 0a03d20133 SVG prefiltering elements based on large faces 2023-06-29 13:29:49 +02:00
Andrej730 f3f56c0891 simplified dev installation script for mac/linux 2023-06-29 16:13:37 +05:00
Andrej730 a689b31279 Fixed crash on Mac m1 on removing unused types (similar to 4d0be1972) 2023-06-29 15:34:52 +05:00
Andrej730 4d0be19720 Fix crash on Mac M1 on removing cost item
Crash happened because we were addressing `.id()` of already deleted `cost_item`.
Surprisingly for me on windows addressing `cost_item` was causing crash too but `cost_item.id()` worked fine.
2023-06-29 14:56:06 +05:00
Andrej730 fb7591caef avoid errors unregistering tools from tests 2023-06-29 13:43:08 +05:00
Dion Moult 8efaf2fbcb Attempt to bundle hppfcl for macosm1 2023-06-29 18:13:52 +10:00
Andrej730 d41068e132 Console warnings if drawing's assets not found #3325 2023-06-29 10:15:02 +05:00
Andrej730 4e65f143da unregistering spatial and structural tools on disabling addon 2023-06-28 16:54:43 +05:00
Andrej730 40656297a6 Fix for f88835b #3271
Now we do not rely on small offset to keep using inner curves to create linings, if some part of the lining is not present (which happens on mullions and transoms) we'll create U or L shape extrusion instead of rectangle with inner curve.

Long story short - this should keep ifc for windows valid.
2023-06-28 15:57:29 +05:00
Andrej730 f88835b87b Corrected L shapes in window lining after e89a9cd #3271
From this - https://i.imgur.com/hQuCcR3.png
To this - https://i.imgur.com/qZ2Lyfo.png

It's a bit hacky since we still use really small offsets to keep "The outer curve shall enclose all inner curves." rule for IfcArbitraryProfileDefWithVoids valid.
2023-06-28 15:56:38 +05:00
Andrej730 8f18db64cf Disable default stream mode for now to avoid errors #3354 2023-06-28 12:38:00 +05:00
Andrej730 fdd473234a Poll message for bim.file_unassociate/bim.file_associate 2023-06-28 10:45:53 +05:00
Andrej730 569f56f713 Fixed error on saving styles without textures after 3aff29dec 2023-06-28 10:39:57 +05:00
Andrej730 61607f9de2 Added IFC override operators to outliner/object context menus #3345
Outliner context menu - https://i.imgur.com/TkJKbLh.png
Object context menu - https://i.imgur.com/JXOEdYH.png
2023-06-28 10:32:33 +05:00
Dion Moult 40b34f6a33 Docs now point to the popular Search IFC Class website. 2023-06-28 13:30:36 +10:00
Dion Moult 89b0674b08 You can now exclude classes from streaming in ifcopenshell.stream 2023-06-28 12:41:50 +10:00
Trashman247 37e4f98857 Implement fully undo/redo with checks
While these actions are short in the backend--taking about 0.01 seconds to run--the Blender UI lags a lot leading to 2-3 second pauses because of the "refresh_brick_viewer" function being slow. This should be investigated.
2023-06-27 13:21:44 -07:00
Andrej730 e89a9cd01c adjust window offset on transoms/mullions for symmetry #3271
Before -  https://i.imgur.com/DbEzJ9H.png
After - https://i.imgur.com/0CERh86.png

Added it for modifier preview and ifc 2d and 3d repr but still need to work some things in 3d representation (L shape lining is not always detected correctly).
2023-06-27 19:30:49 +05:00
Dion Moult 20b63f530a New option to enable streaming IFC data into BBIM.
To test, go into add-on preferences and enable "Should Stream". Then load an IFC-SPF in advanced mode, and disable "Load Geometry" before loading (the geometry iterator doesn't work with streaming).
2023-06-27 21:07:37 +10:00
Dion Moult 183230b7f5 Naive implementation of file stream and Lark based IFC-SPF reader to handle huge IFCs. 2023-06-27 21:03:02 +10:00
Andrej730 7e2b40176c Fixing tests after 2dc9dc200 2023-06-27 15:29:05 +05:00
Andrej730 3aff29dec1 Support for import IfcSurfaceStyleWithTextures
Added UI for adding textures in BBIM shader graph generator

https://imgur.com/a/BI5pOZn
2023-06-27 14:30:30 +05:00
Trashman247 2ce32c6521 Change namespace selector to only show alias 2023-06-26 23:17:11 -07:00
Trashman247 9d84172f15 Add changeset versioning to Brick add/remove 2023-06-26 23:16:52 -07:00
Trashman247 8341931a62 Reformat commenting, remove prints, and remove BrickStore.project 2023-06-26 16:01:26 -07:00
Trashman247 81a4ff711c Implement fully serialize operator
- Turns out, you need the keyword "graph=" in graph_at() to actually select a graph of that name from the collection, otherwise it just returns the entire collection, so I changed that, which correctly isolates the project from the collection for serialization now.
- With this same change, I opted turn BrickStore.VersionedGraphCollection simply into BrickStore.graph and create a new BrickStore.get_project() to return the isolated graph. - This meant I should remove the reload_graph() function because I was actually just loading the entire collection into it still, and its functionality breaks when it isn't the entire collection
2023-06-26 13:59:28 -07:00
Dion Moult 2da86c170f See #3347. Hotfix latest IfcOpenShell SQLite features when making builds. 2023-06-26 22:14:59 +10:00
Dion Moult 41a176cb6d The BlenderBIM Add-on now supports loading IFCSQLite files! 2023-06-26 17:21:01 +10:00
Dion Moult 10e96239a1 You can now toggle loading geometry in advanced mode when loading a project 2023-06-26 17:13:34 +10:00
Dion Moult 897dfb1e33 ifcopenshell.sqlite wrapper now handles JSON fields by default, which is significantly more performant
f attributes holding entity lists are expanded in SQL, this means that you end up with significantly more rows. Also, you cannot have the ifc_id marked as a UNIQUE field / benefit from unique primary key index. So leaving the fields as JSON is actually significantly more efficient (almost 10X faster to load a model into BBIM).
2023-06-26 17:08:50 +10:00
Dion Moult c1d972f7bf Ifc2Sql patch now can store inverses for efficient queries. 2023-06-26 17:08:50 +10:00
Trashman247 d9196882dc Implement early testing for serialize operator (not functional)
All the right code seems to be in place, but it seems the package won't go through with the serialize function because of read/write permissions (ERRNO 13)
2023-06-25 23:56:15 -07:00
Trashman247 580f8f6218 Change graph naming to all caps 2023-06-25 23:21:27 -07:00
Trashman247 e177f5f051 Add Brick.ttl to .gitignore 2023-06-25 23:20:56 -07:00
Andrej730 1a5c501af0 Fixed typo in e3a7f7627 2023-06-26 10:48:50 +05:00
Trashman247 046ce2b8e7 Update BrickStore.purge()
I wanted to just have a BrickStore.clear() which would so this:

        BrickStore.VersionedGraphCollection = None
        BrickStore.graph = None
        BrickStore.path = None

(aka not also set BrickStore.schema = None, since it should theoretically just load in the same path anyway) but for some reason Blender crashes when clearing a project and loading one again this way.
2023-06-23 16:55:34 -07:00
Trashman247 bcf5f07718 Rework NewBrick and LoadBrick in test_brick.py to fit with new VGC format
- VGC means VersionedGraphCollection.
- BrickStore.schema is now a filepath, so make that the case in the testing also.
2023-06-19 21:59:17 -07:00
Trashman247 a3572f3372 Implement VersionedGraphCollection to Brick module
- Reworked `load_brick_file` and `new_brick_file` to fit under the VersionedGraphCollection implementation. (Other methods may have broken).
- Added `undo_brick` and `redo_brick`.
- Kept BrickStore.graph the same as to generally still work with the rest of the code. This was done by parsing the VersionedGraphCollection with the new `reload_brick_graph` method.
2023-06-19 21:54:28 -07:00
341 changed files with 64929 additions and 24592 deletions
+1 -1
View File
@@ -39,7 +39,7 @@ jobs:
strategy:
fail-fast: false
matrix:
pyver: [py37, py39, py310]
pyver: [py39, py310]
config:
- {
name: "Windows Build",
+1 -1
View File
@@ -12,7 +12,7 @@ on:
push:
paths:
- '.github/workflows/ci-ifctester-pypi.yml'
workflow_dispatch:
env:
major: 0
+4 -1
View File
@@ -94,4 +94,7 @@ src/ifcopenshell-python/ifcopenshell/_ifcopenshell_wrapper.so
src/ifcopenshell-python/ifcopenshell/ifcopenshell_wrapper.py
# apple
.DS_Store
.DS_Store
# Brickschema
src/blenderbim/blenderbim/bim/schema/Brick.ttl
+2 -1
View File
@@ -1,8 +1,9 @@
IfcOpenShell
============
<p align="center">
<img src="https://github.com/IfcOpenShell/IfcOpenShell/assets/79010126/9e540403-c79f-446a-88c6-7c1ebc6ba864">
<img src="https://github.com/IfcOpenShell/IfcOpenShell/assets/88302/34901387-e2dd-4a0c-8e38-9ffc32a66cde">
</p>
IfcOpenShell is an open source ([LGPL]) software library for working with Industry Foundation Classes ([IFC]). Complete
-48
View File
@@ -1,48 +0,0 @@
Dockerized AWS Lambda Function with Python and IfcOpenShell
===========================================================
This guide provides a Dockerfile and sample code to help you run an AWS Lambda function written in Python and utilizing IfcOpenShell library.
Prerequisites
-------------
Make sure you have Docker installed on your machine. You can download Docker from the official website: [Docker Instalation](https://docs.docker.com/engine/install/)
Getting Started
---------------
1. **Clone this repository to your local machine:**
2. **Customize the Lambda function code:**
- Replace the sample Lambda function code in the `example_handler` directory with your own code.
- Update the import path in the Dockerfile's `CMD` instruction to match your Lambda function's handler function.
3. **Install the required Python packages:**
- Edit the `requirements.txt` file and add any additional dependencies required by your Lambda function.
4. **Build the Docker image:**
```shell
$ docker build -t lambda-ifcopenshell .
```
5. **Run the Docker container:**
```shell
$ docker run lambda-ifcopenshell
```
6. **Test lambda locally**
Follow the steps in this [guide](https://docs.aws.amazon.com/lambda/latest/dg/images-test.html)
7. **Deploy to lambda**
This is beyond the scope of this example. Please refer to AWS documentation. Some tools that could be useful are - AWS CloudFormaton, AWS CDK, pulumi or terraform
---------
> Note: This is a superficial guide, in order to make this work for your usecase you will need a good understanding of other AWS services like SQS, S3, API Gateway etc.
For more information on building lambda containers refer to this [guide](https://docs.aws.amazon.com/lambda/latest/dg/images-create.html).
+49 -40
View File
@@ -13,68 +13,77 @@ def request_repo_info(url: str):
return resp
def get_choco_package_info() -> str:
resp = request_repo_info(URL_CHOCO_PACKAGE)
html_txt = str(resp.read())
return html_txt
def get_latest_blender_version() -> list:
html_txt = get_choco_package_info()
return re.findall(RE_BLENDER_VERSION_MIN_MAJ_PAT, html_txt)
URL_IFCOS_RELEASES = "https://github.com/IfcOpenShell/IfcOpenShell/releases"
URL_CHOCO_PACKAGE = "https://community.chocolatey.org/packages/blender"
URL_BLENDER_CMAKE = "https://raw.githubusercontent.com/blender/blender/{}/build_files/cmake/Modules/FindPythonLibsUnix.cmake"
RE_BLENDER_VERSION_MIN_MAJ = r"Latest Version.+<span>Blender (\d+\.\d+)\..+</span>"
RE_BLENDER_VERSION_MIN_MAJ_PAT = r"Latest Version.+<span>Blender (\d+\.\d+\.\d+)</span>"
RE_BLENDER_PYTHON_VERSION_MAJ_MIN = r"SET\(_PYTHON_VERSION_SUPPORTED (\d+\.\d+)\)"
if sys.argv[1] == "--do_choco_release?":
now = datetime.datetime.now()
blenderbim_date = (now - datetime.timedelta(days=1)).strftime("%y%m%d")
url = "https://github.com/IfcOpenShell/IfcOpenShell/releases"
resp = request_repo_info(url)
resp = request_repo_info(URL_IFCOS_RELEASES)
text = str(resp.read())
if blenderbim_date in text:
print("do_choco_release", end="")
elif sys.argv[1] == "--latest_blender_release_maj_min_pat?":
re_blender_version_min_maj_pat = r"Latest Version.+<span>Blender (\d+\.\d+\.\d+)</span>"
url = "https://community.chocolatey.org/packages/blender"
resp = request_repo_info(url)
html_txt = str(resp.read())
found = re.findall(re_blender_version_min_maj_pat, html_txt)
if found:
print(found[0], end="")
latest_blender_version = get_latest_blender_version()
if latest_blender_version:
print(latest_blender_version[0], end="")
else:
print("[ERROR] could not determine blender_version_min_maj_pat")
quit(1)
elif sys.argv[1] == "--latest_blender_release_maj_min?":
re_blender_version_min_maj = r"Latest Version.+<span>Blender (\d+\.\d+)\..+</span>"
url = "https://community.chocolatey.org/packages/blender"
resp = request_repo_info(url)
html_txt = str(resp.read())
found = re.findall(re_blender_version_min_maj, html_txt)
if found:
print(found[0], end="")
html_txt = get_choco_package_info()
blender_version_min_maj = re.findall(RE_BLENDER_VERSION_MIN_MAJ, html_txt)
if blender_version_min_maj:
print(blender_version_min_maj[0], end="")
else:
print("[ERROR] could not determine blender_version_min_maj")
quit(1)
elif sys.argv[1] == "--latest_blender_python_version_maj_min?":
# get latest blender version first
re_blender_version_min_maj_pat = r"Latest Version.+<span>Blender (\d+\.\d+\.\d+)</span>"
url = "https://community.chocolatey.org/packages/blender"
resp = request_repo_info(url)
html_txt = str(resp.read())
found = re.findall(re_blender_version_min_maj_pat, html_txt)
if found:
latest_blender_version_tag = f"v{found[0]}"
re_blender_python_version_maj_min = r"SET\(PYTHON_VERSION (\d+.\d+) "
url = f"https://raw.githubusercontent.com/blender/blender/{latest_blender_version_tag}/build_files/cmake/Modules/FindPythonLibsUnix.cmake"
resp = request_repo_info(url)
latest_blender_version = get_latest_blender_version()
if latest_blender_version:
latest_blender_version_tag = f"v{latest_blender_version[0]}"
resp = request_repo_info(URL_BLENDER_CMAKE.format(latest_blender_version_tag))
html_txt = str(resp.read())
found = re.findall(re_blender_python_version_maj_min, html_txt)
found = re.findall(RE_BLENDER_PYTHON_VERSION_MAJ_MIN, html_txt)
if found:
print(found[0], end="")
else:
print("[ERROR] could not determine blender_python_version_maj_min")
quit(1)
elif sys.argv[1] == "--pyver?":
# get latest blender version first
re_blender_version_min_maj_pat = r"Latest Version.+<span>Blender (\d+\.\d+\.\d+)</span>"
url = "https://community.chocolatey.org/packages/blender"
resp = request_repo_info(url)
html_txt = str(resp.read())
found = re.findall(re_blender_version_min_maj_pat, html_txt)
if found:
latest_blender_version_tag = f"v{found[0]}"
re_blender_python_version_maj_min = r"SET\(PYTHON_VERSION (\d+.\d+) "
url = f"https://raw.githubusercontent.com/blender/blender/{latest_blender_version_tag}/build_files/cmake/Modules/FindPythonLibsUnix.cmake"
resp = request_repo_info(url)
latest_blender_version = get_latest_blender_version()
if latest_blender_version:
latest_blender_version_tag = f"v{latest_blender_version[0]}"
resp = request_repo_info(URL_BLENDER_CMAKE.format(latest_blender_version_tag))
html_txt = str(resp.read())
found = re.findall(re_blender_python_version_maj_min, html_txt)
found = re.findall(RE_BLENDER_PYTHON_VERSION_MAJ_MIN, html_txt)
if found:
print(f"py{found[0].replace('.', '')}", end="")
else:
print("[ERROR] could not determine pyver")
quit(1)
+1
View File
@@ -4,6 +4,7 @@ set MY_PY_VER=%PY_VER:.=%
set LIBXML2="%LIBRARY_PREFIX%/lib/libxml2.lib"
cmake -G "Ninja" ^
-D SCHEMA_VERSIONS="2x3;4;4x1;4x3;4x3_add1" ^
-D CMAKE_BUILD_TYPE:STRING=Release ^
-D CMAKE_INSTALL_PREFIX:FILEPATH="%LIBRARY_PREFIX%" ^
-D CMAKE_PREFIX_PATH:FILEPATH="%LIBRARY_PREFIX%" ^
+2 -1
View File
@@ -13,6 +13,7 @@ if [ `uname` == Darwin ]; then
fi
cmake -G Ninja \
-DSCHEMA_VERSIONS="2x3;4;4x1;4x3;4x3_add1" \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_INSTALL_PREFIX=$PREFIX \
${CMAKE_PLATFORM_FLAGS[@]} \
@@ -41,4 +42,4 @@ cmake -G Ninja \
ninja
ninja install -j 2
ninja install -j 1
+3 -3
View File
@@ -36,10 +36,10 @@ requirements:
run:
- python
- boost-cpp
- occt ==7.7.0
- {{ pin_compatible('occt', max_pin='x.x.x') }}
- {{ pin_compatible('cgal-cpp', max_pin='x.x.x') }}
- {{ pin_compatible('boost-cpp', max_pin='x.x.x') }}
- libxml2
- cgal-cpp
- hdf5
- mpfr
- gmp # [unix]
+2
View File
@@ -275,6 +275,7 @@ class TopicHandler:
"""
new_viewpoint = VisualizationInfoHandler.create_new(element, self._xml_handler)
self.add_visinfo_handler(new_viewpoint)
return new_viewpoint
def add_viewpoint_from_point_and_guids(self, position: NDArray[np.float_], *guids: str) -> None:
"""Add a viewpoint pointing at an XYZ point in space
@@ -287,6 +288,7 @@ class TopicHandler:
position, *guids, xml_handler=self._xml_handler
)
self.add_visinfo_handler(vi_handler)
return vi_handler
def add_visinfo_handler(self, new_viewpoint: VisualizationInfoHandler) -> None:
self.viewpoints[new_viewpoint.guid + ".bcfv"] = new_viewpoint
+104 -74
View File
@@ -36,12 +36,9 @@ endif
endif
VERSION:=`date '+%y%m%d'`
PYVERSION:=py37
LAST_COMMIT_HASH:=$(shell git rev-parse HEAD)
PYVERSION:=py310
ifeq ($(PYVERSION), py37)
PYLIBDIR:=python3.7
PYNUMBER:=37
endif
ifeq ($(PYVERSION), py39)
PYLIBDIR:=python3.9
PYNUMBER:=39
@@ -52,123 +49,100 @@ PYNUMBER:=310
endif
ifeq ($(PLATFORM), linux)
ifeq ($(PYVERSION), py37)
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.7.5/download/linux-64/hpp-fcl-1.7.5-py37h5f1835d_0.tar.bz2
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/1d/a4/931d0780f31f3ea8c4f9ef6464a2825137c5241e6707a5fb03bef760a7eb/shapely-2.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_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
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/2.3.4/download/linux-64/hpp-fcl-2.3.4-py39h40a70d0_0.conda
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/3.1.0/download/linux-64/eigenpy-3.1.0-py39hdfdd6bb_0.conda
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.78.0/download/linux-64/boost-1.78.0-py39h7c9e3ff_4.tar.bz2
QHULL_URL:=https://anaconda.org/conda-forge/qhull/2020.2/download/linux-64/qhull-2020.2-h4bd325d_2.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/2d/f2/8ec281d357e8bb7d08dc8d727f0e4c8ef3dae7d3fa75c69c8e452bb82d50/shapely-2.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_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
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/2.3.4/download/linux-64/hpp-fcl-2.3.4-py310h995690b_0.conda
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/3.1.0/download/linux-64/eigenpy-3.1.0-py310hf02b7e0_0.conda
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.78.0/download/linux-64/boost-1.78.0-py310hc4a4660_4.tar.bz2
QHULL_URL:=https://anaconda.org/conda-forge/qhull/2020.2/download/linux-64/qhull-2020.2-h4bd325d_2.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/a8/a5/403728b5614b28083f6424dfdefec5fcf58068495fb03bb08532671c642f/shapely-2.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_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
OCTOMAP_URL:=https://anaconda.org/conda-forge/octomap/1.9.8/download/linux-64/octomap-1.9.8-h924138e_0.tar.bz2
BOOSTCPP_URL:=https://anaconda.org/conda-forge/boost-cpp/1.78.0/download/linux-64/boost-cpp-1.78.0-h6582d0a_3.conda
ZLIB_URL:=https://anaconda.org/conda-forge/zlib/1.2.11/download/linux-64/zlib-1.2.11-h516909a_1010.tar.bz2
endif
ifeq ($(PLATFORM), macos)
ifeq ($(PYVERSION), py37)
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://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/e6/7d/4923f27c340339e1c896c77cafc8ed672c8d381a025bbab6c6ddcba27e8f/shapely-2.0.1-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
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/2.3.4/download/osx-64/hpp-fcl-2.3.4-py39hd85b194_0.conda
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/3.1.0/download/osx-64/eigenpy-3.1.0-py39hc4d6e28_0.conda
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.78.0/download/osx-64/boost-1.78.0-py39h953a6b8_4.tar.bz2
QHULL_URL:=https://anaconda.org/conda-forge/qhull/2020.2/download/osx-64/qhull-2020.2-h940c156_2.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/36/a4/7e542a209f862f967d7cb8e939eff155f4294a27d17e16441fb8bdd51a2c/shapely-2.0.1-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
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/2.3.4/download/osx-64/hpp-fcl-2.3.4-py310h1db6f5f_0.conda
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/3.1.0/download/osx-64/eigenpy-3.1.0-py310h43da829_0.conda
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.78.0/download/osx-64/boost-1.78.0-py310h3e792ce_4.tar.bz2
QHULL_URL:=https://anaconda.org/conda-forge/qhull/2020.2/download/osx-64/qhull-2020.2-h940c156_2.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/1f/2a/dc3353c2431cf53e8d04bb8fba27e584410ca3435c9c85f76d71bf0c0e80/shapely-2.0.1-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
OCTOMAP_URL:=https://anaconda.org/conda-forge/octomap/1.9.8/download/osx-64/octomap-1.9.8-hb8565cd_0.tar.bz2
BOOSTCPP_URL:=https://anaconda.org/conda-forge/boost-cpp/1.78.0/download/osx-64/boost-cpp-1.78.0-hf5ba120_3.conda
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/e6/7d/4923f27c340339e1c896c77cafc8ed672c8d381a025bbab6c6ddcba27e8f/shapely-2.0.1-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
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/2.3.4/download/osx-arm64/hpp-fcl-2.3.4-py39hc34188a_0.conda
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/3.1.0/download/osx-arm64/eigenpy-3.1.0-py39h13cfc01_0.conda
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.78.0/download/osx-arm64/boost-1.78.0-py39h99de9ae_4.tar.bz2
QHULL_URL:=https://anaconda.org/conda-forge/qhull/2020.2/download/osx-arm64/qhull-2020.2-hc021e02_2.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/ea/aa/45fbd031edf3149cb767d8b9f9db45d5faf0324d743c6b8fb0298cc022d0/shapely-2.0.1-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
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/2.3.4/download/osx-arm64/hpp-fcl-2.3.4-py310h46fc4cd_0.conda
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/3.1.0/download/osx-arm64/eigenpy-3.1.0-py310ha2643af_0.conda
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.78.0/download/osx-arm64/boost-1.78.0-py310h629746b_4.tar.bz2
QHULL_URL:=https://anaconda.org/conda-forge/qhull/2020.2/download/osx-arm64/qhull-2020.2-hc021e02_2.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/ec/41/d59208743e737184e1b403e95a937aebb022b8459e99efbcd5208fc8be46/shapely-2.0.1-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
OCTOMAP_URL:=https://anaconda.org/conda-forge/octomap/1.9.8/download/osx-arm64/octomap-1.9.8-hffc8910_0.tar.bz2
BOOSTCPP_URL:=https://anaconda.org/conda-forge/boost-cpp/1.78.0/download/osx-arm64/boost-cpp-1.78.0-h9ed8d21_3.conda
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/e2/87/b8b8d8d57b429b01aa56b7d723075c09f33c988b8091bb6f790c83436909/shapely-2.0.1-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
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/2.3.4/download/win-64/hpp-fcl-2.3.4-py39h39b25cb_0.conda
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/3.1.0/download/win-64/eigenpy-3.1.0-py39hb6c915b_0.conda
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.78.0/download/win-64/boost-1.78.0-py39hea4d8d1_4.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/a7/ae/eab645c4075093584b7a65ab71cb8ff4563a015bd935c9b55dba14b2c1eb/shapely-2.0.1-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
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/2.3.4/download/win-64/hpp-fcl-2.3.4-py310heb59267_0.conda
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/3.1.0/download/win-64/eigenpy-3.1.0-py310hd146abe_0.conda
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.78.0/download/win-64/boost-1.78.0-py310h220cb41_4.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/81/8a/7ac076a86b2632f1872284c5e60ed5f2fc26094875a85b35d9fa17b52504/shapely-2.0.1-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
OCTOMAP_URL:=https://anaconda.org/conda-forge/octomap/1.9.8/download/win-64/octomap-1.9.8-h91493d7_0.tar.bz2
BOOSTCPP_URL:=https://anaconda.org/conda-forge/boost-cpp/1.78.0/download/win-64/boost-cpp-1.78.0-h9f4b32c_3.conda
ZLIB_URL:=https://anaconda.org/conda-forge/zlib/1.2.11/download/win-64/zlib-1.2.11-h62dcd97_1010.tar.bz2
endif
@@ -190,7 +164,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-8f41ae0-$(PLATFORM)64.zip
cd dist/working && wget https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-$(PYNUMBER)-v0.7.0-fc50bdd-$(PLATFORM)64.zip
cd dist/working && unzip ifcopenshell-python*
cp -r dist/working/ifcopenshell dist/blenderbim/libs/site/packages/
@@ -206,9 +180,7 @@ endif
# IfcOpenBot sometimes lags behind, so we hotfix the Python utilities
cp -r dist/working/IfcOpenShell-0.7.0/src/ifcopenshell-python/ifcopenshell/util/* dist/blenderbim/libs/site/packages/ifcopenshell/util/
cp -r dist/working/IfcOpenShell-0.7.0/src/ifcopenshell-python/ifcopenshell/api/* dist/blenderbim/libs/site/packages/ifcopenshell/api/
cp dist/working/IfcOpenShell-0.7.0/src/ifcopenshell-python/ifcopenshell/entity_instance.py dist/blenderbim/libs/site/packages/ifcopenshell/
cp dist/working/IfcOpenShell-0.7.0/src/ifcopenshell-python/ifcopenshell/file.py dist/blenderbim/libs/site/packages/ifcopenshell/
cp dist/working/IfcOpenShell-0.7.0/src/ifcopenshell-python/ifcopenshell/validate.py dist/blenderbim/libs/site/packages/ifcopenshell/
cp dist/working/IfcOpenShell-0.7.0/src/ifcopenshell-python/ifcopenshell/*.py dist/blenderbim/libs/site/packages/ifcopenshell/
cp dist/working/IfcOpenShell-0.7.0/src/ifcopenshell-python/ifcopenshell/express/rule_executor.py dist/blenderbim/libs/site/packages/ifcopenshell/express/
# Provides bcf functionality
cp -r dist/working/IfcOpenShell-0.7.0/src/bcf/src/bcf dist/blenderbim/libs/site/packages/
@@ -416,7 +388,8 @@ endif
# Required by IFCClash
mkdir dist/working
cd dist/working && wget $(HPPFCL_URL)
cd dist/working && tar -xf hpp-fcl*
cd dist/working && unzip hpp-fcl*
cd dist/working && tar -I zstd -xvf pkg*
ifeq ($(PLATFORM), linux)
cp -r dist/working/lib/$(PYLIBDIR)/site-packages/hppfcl dist/blenderbim/libs/site/packages/
cp -r dist/working/lib/*.so* dist/blenderbim/libs/
@@ -425,6 +398,10 @@ ifeq ($(PLATFORM), macos)
cp -r dist/working/lib/$(PYLIBDIR)/site-packages/hppfcl dist/blenderbim/libs/site/packages/
cp -r dist/working/lib/*.dylib dist/blenderbim/libs/
endif
ifeq ($(PLATFORM), macosm1)
cp -r dist/working/lib/$(PYLIBDIR)/site-packages/hppfcl dist/blenderbim/libs/site/packages/
cp -r dist/working/lib/*.dylib dist/blenderbim/libs/
endif
ifeq ($(PLATFORM), win)
cp -r dist/working/Lib/site-packages/hppfcl dist/blenderbim/libs/site/packages/
cp -r dist/working/Library/bin/*.dll dist/blenderbim/libs/site/packages/hppfcl/
@@ -441,21 +418,45 @@ endif
ifeq ($(PLATFORM), macos)
cp -r dist/working/lib/*.dylib dist/blenderbim/libs/
endif
ifeq ($(PLATFORM), macosm1)
cp -r dist/working/lib/*.dylib dist/blenderbim/libs/
endif
ifeq ($(PLATFORM), win)
cp -r dist/working/Library/bin/*.dll dist/blenderbim/libs/site/packages/hppfcl/
endif
rm -rf dist/working
# Required by hpp-fcl
# Required by hpp-fcl except on Windows
ifneq ($(PLATFORM), win)
mkdir dist/working
cd dist/working && wget $(EIGENPY_URL)
cd dist/working && tar -xf eigenpy*
cd dist/working && wget $(QHULL_URL)
cd dist/working && tar -xf qhull*
ifeq ($(PLATFORM), linux)
cp -r dist/working/lib/*.so* dist/blenderbim/libs/
endif
ifeq ($(PLATFORM), macos)
cp -r dist/working/lib/*.dylib dist/blenderbim/libs/
endif
ifeq ($(PLATFORM), macosm1)
cp -r dist/working/lib/*.dylib dist/blenderbim/libs/
endif
rm -rf dist/working
endif
# Required by hpp-fcl
mkdir dist/working
cd dist/working && wget $(EIGENPY_URL)
cd dist/working && unzip eigenpy*
cd dist/working && tar -I zstd -xvf pkg*
ifeq ($(PLATFORM), linux)
cp -r dist/working/lib/*.so* dist/blenderbim/libs/
endif
ifeq ($(PLATFORM), macos)
cp -r dist/working/lib/*.dylib dist/blenderbim/libs/
endif
ifeq ($(PLATFORM), macosm1)
cp -r dist/working/lib/*.dylib dist/blenderbim/libs/
endif
ifeq ($(PLATFORM), win)
cp -r dist/working/Library/bin/*.dll dist/blenderbim/libs/site/packages/hppfcl/
endif
@@ -471,6 +472,9 @@ endif
ifeq ($(PLATFORM), macos)
cp -r dist/working/lib/*.dylib dist/blenderbim/libs/
endif
ifeq ($(PLATFORM), macosm1)
cp -r dist/working/lib/*.dylib dist/blenderbim/libs/
endif
ifeq ($(PLATFORM), win)
# Uh, do nothing, apparently? No DLLs are shipped.
endif
@@ -486,11 +490,33 @@ endif
ifeq ($(PLATFORM), macos)
cp -r dist/working/lib/*.dylib dist/blenderbim/libs/
endif
ifeq ($(PLATFORM), macosm1)
cp -r dist/working/lib/*.dylib dist/blenderbim/libs/
endif
ifeq ($(PLATFORM), win)
cp -r dist/working/Library/bin/*.dll dist/blenderbim/libs/site/packages/hppfcl/
endif
rm -rf dist/working
# Required by hpp-fcl
mkdir dist/working
cd dist/working && wget $(BOOSTCPP_URL)
cd dist/working && unzip boost-cpp*
cd dist/working && tar -I zstd -xvf pkg*
ifeq ($(PLATFORM), linux)
cp -r dist/working/lib/libboost_serialization.so* dist/blenderbim/libs/
endif
ifeq ($(PLATFORM), macos)
cp -r dist/working/lib/libboost_serialization.dylib dist/blenderbim/libs/
endif
ifeq ($(PLATFORM), macosm1)
cp -r dist/working/lib/libboost_serialization.dylib dist/blenderbim/libs/
endif
ifeq ($(PLATFORM), win)
cp -r dist/working/Library/bin/boost_serialization.dll dist/blenderbim/libs/site/packages/hppfcl/
endif
rm -rf dist/working
# Required by hpp-fcl
mkdir dist/working
cd dist/working && wget $(ZLIB_URL)
@@ -501,6 +527,9 @@ endif
ifeq ($(PLATFORM), macos)
cp -r dist/working/lib/*.dylib dist/blenderbim/libs/
endif
ifeq ($(PLATFORM), macosm1)
cp -r dist/working/lib/*.dylib dist/blenderbim/libs/
endif
ifeq ($(PLATFORM), win)
cp -r dist/working/Library/bin/*.dll dist/blenderbim/libs/site/packages/hppfcl/
endif
@@ -647,6 +676,7 @@ endif
rm -rf dist/blenderbim/libs/site/packages/numpy.libs
cd dist/blenderbim && $(SED) "s/999999/$(VERSION)/" __init__.py
cd dist/blenderbim/bim && $(SED) "s/8888888/$(LAST_COMMIT_HASH)/" __init__.py
cd dist && zip -r blenderbim-$(VERSION)-$(PYVERSION)-$(PLATFORM).zip ./*
rm -rf dist/blenderbim
+5 -4
View File
@@ -22,13 +22,14 @@ import site
bl_info = {
"name": "BlenderBIM",
"description": "Author, import, and export data using the Industry Foundation Classes schema",
"description": "Transforms Blender into a native Building Information Model authoring platform using IFC.",
"author": "IfcOpenShell Contributors",
"blender": (2, 80, 0),
"blender": (3, 1, 0),
"version": (0, 0, 999999),
"location": "File > Export, File > Import, Scene / Object / Material / Mesh Properties",
"location": "File Menu, Scene Properties Tab. See documentation for more.",
"doc_url": "https://blenderbim.org/docs",
"tracker_url": "https://github.com/IfcOpenShell/IfcOpenShell/issues",
"category": "Import-Export",
"category": "System",
}
if sys.modules.get("bpy", None):
+106 -26
View File
@@ -16,11 +16,16 @@
# 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 os
from pathlib import Path
import bpy
import bpy.utils.previews
import blenderbim
import importlib
from . import handler, ui, prop, operator, helper
cwd = os.path.dirname(os.path.realpath(__file__))
modules = {
"project": None,
"search": None,
@@ -86,9 +91,10 @@ classes = [
operator.AddIfcFile,
operator.BIM_OT_add_section_plane,
operator.BIM_OT_remove_section_plane,
operator.ConfigureVisibility,
operator.OpenUpstream,
operator.OpenUri,
operator.SwitchTab,
operator.SetTab,
operator.ReloadIfcFile,
operator.RemoveIfcFile,
operator.SelectDataDir,
@@ -105,7 +111,7 @@ classes = [
operator.BIM_OT_enum_property_search, # /!\ Register AFTER prop.StrProperty
prop.ObjProperty,
prop.Attribute,
prop.ModuleVisibility,
prop.BIMAreaProperties,
prop.BIMProperties,
prop.IfcParameter,
prop.PsetQto,
@@ -114,51 +120,75 @@ classes = [
prop.BIMCollectionProperties,
prop.BIMMaterialProperties,
prop.BIMMeshProperties,
ui.BIM_PT_section_plane,
ui.BIM_UL_generic,
ui.BIM_UL_topics,
ui.BIM_ADDON_preferences,
# Scene panel groups
# Project overview
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,
ui.BIM_PT_4D5D,
ui.BIM_PT_quality_control,
ui.BIM_PT_integrations,
# Object panel groups
ui.BIM_PT_object_metadata,
ui.BIM_PT_geometry_object,
ui.BIM_PT_services_object,
ui.BIM_PT_utilities_object,
ui.BIM_PT_misc_object,
ui.BIM_PT_tab_grouping_and_filtering,
# Tabs panel
ui.BIM_PT_tabs,
# Object information
ui.BIM_PT_tab_object_metadata,
ui.BIM_PT_tab_misc,
# Geometry and materials
ui.BIM_PT_tab_representations,
ui.BIM_PT_tab_geometric_relationships,
ui.BIM_PT_tab_parametric_geometry,
ui.BIM_PT_tab_materials,
ui.BIM_PT_tab_styles,
# Drawings and documents
# Services and systems
ui.BIM_PT_tab_services,
ui.BIM_PT_tab_services_object,
# Structural analysis
ui.BIM_PT_tab_structural,
# Construction scheduling
ui.BIM_PT_tab_4D5D,
# Facility management
ui.BIM_PT_tab_handover,
ui.BIM_PT_tab_operations,
# Quality and coordination
ui.BIM_PT_tab_quality_control,
ui.BIM_PT_tab_collaboration,
ui.BIM_PT_tab_sandbox,
# TODO: move this somewhere else and clean it up
ui.BIM_PT_section_plane,
]
for mod in modules.values():
classes.extend(mod.classes)
addon_keymaps = []
icons = None
is_registering = False
last_commit_hash = "8888888"
def on_register(scene):
handler.setDefaultProperties(scene)
global is_registering
if is_registering:
return
is_registering = True
handler.load_post(scene)
if not bpy.app.background:
bpy.app.handlers.depsgraph_update_post.remove(on_register)
is_registering = False
def register():
for cls in classes:
bpy.utils.register_class(cls)
bpy.app.handlers.depsgraph_update_post.append(on_register)
bpy.app.handlers.undo_pre.append(handler.undo_pre)
bpy.app.handlers.undo_post.append(handler.undo_post)
bpy.app.handlers.redo_pre.append(handler.redo_pre)
bpy.app.handlers.redo_post.append(handler.redo_post)
bpy.app.handlers.load_post.append(handler.setDefaultProperties)
bpy.app.handlers.load_post.append(handler.load_post)
bpy.app.handlers.load_post.append(handler.loadIfcStore)
bpy.app.handlers.save_post.append(handler.ensureIfcExported)
bpy.types.Scene.BIMProperties = bpy.props.PointerProperty(type=prop.BIMProperties)
bpy.types.Screen.BIMAreaProperties = bpy.props.CollectionProperty(type=prop.BIMAreaProperties)
bpy.types.Collection.BIMCollectionProperties = bpy.props.PointerProperty(type=prop.BIMCollectionProperties)
bpy.types.Object.BIMObjectProperties = bpy.props.PointerProperty(type=prop.BIMObjectProperties)
bpy.types.Material.BIMObjectProperties = bpy.props.PointerProperty(type=prop.BIMObjectProperties)
@@ -167,24 +197,53 @@ def register():
bpy.types.Curve.BIMMeshProperties = bpy.props.PointerProperty(type=prop.BIMMeshProperties)
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)
bpy.types.STATUSBAR_HT_header.append(ui.draw_statusbar)
for mod in modules.values():
mod.register()
wm = bpy.context.window_manager
if wm.keyconfigs.addon:
km = wm.keyconfigs.addon.keymaps.new(name='Window', space_type='EMPTY')
kmi = km.keymap_items.new('bim.switch_tab', 'TAB', 'PRESS', ctrl=True)
addon_keymaps.append((km, kmi))
global icons
icons_dir = os.path.join(cwd, "data", "icons")
icon_preview = bpy.utils.previews.new()
for filename in os.listdir(icons_dir):
if filename.endswith(".png"):
icon_name = os.path.splitext(filename)[0]
icon_path = os.path.join(icons_dir, filename)
icon_preview.load(icon_name, icon_path, "IMAGE")
icons = icon_preview
global last_commit_hash
try:
import git
path = Path(__file__).resolve().parent
repo = git.Repo(str(path), search_parent_directories=True)
last_commit_hash = repo.head.object.hexsha
except:
pass
def unregister():
global icons
bpy.utils.previews.remove(icons)
for cls in reversed(classes):
if getattr(cls, "is_registered", None) is None:
bpy.utils.unregister_class(cls)
elif cls.is_registered:
bpy.utils.unregister_class(cls)
bpy.app.handlers.load_post.remove(handler.setDefaultProperties)
bpy.app.handlers.load_post.remove(handler.load_post)
bpy.app.handlers.load_post.remove(handler.loadIfcStore)
bpy.app.handlers.save_post.remove(handler.ensureIfcExported)
del bpy.types.Scene.BIMProperties
del bpy.types.Collection.BIMCollectionProperties
del bpy.types.Object.BIMObjectProperties
@@ -194,9 +253,30 @@ def unregister():
del bpy.types.Curve.BIMMeshProperties
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)
bpy.types.STATUSBAR_HT_header.remove(ui.draw_statusbar)
for mod in reversed(list(modules.values())):
mod.unregister()
wm = bpy.context.window_manager
kc = wm.keyconfigs.addon
if kc:
for km, kmi in addon_keymaps:
km.keymap_items.remove(kmi)
addon_keymaps.clear()
for panel in [
"SCENE_PT_scene",
"SCENE_PT_unit",
"SCENE_PT_physics",
"SCENE_PT_rigid_body_world",
"SCENE_PT_audio",
"SCENE_PT_keying_sets",
"SCENE_PT_custom_props",
]:
try:
bpy.utils.unregister_class(getattr(handler, f"Override_{panel}"))
except:
pass
@@ -27,6 +27,12 @@ text, tspan { /* 2.5mm */ fill: black; stroke: none; font-family: 'OpenGost Type
.IfcAnnotation { fill: none; stroke: black; stroke-linecap: 'round'; stroke-width: 0.25; }
.IfcGeographicElement { fill: none; stroke: black; stroke-linecap: 'round'; stroke-width: 1; }
.PredefinedType-LINEWORK { stroke: black; stroke-width: 0.25; }
.PredefinedType-LINEWORK.dashed { stroke-dasharray: 3, 2; }
.PredefinedType-LINEWORK.fine { stroke-width: 0.18; stroke: #777777; }
.PredefinedType-LINEWORK.thin { stroke-width: 0.25; }
.PredefinedType-LINEWORK.medium { stroke-width: 0.35; }
.PredefinedType-LINEWORK.thick { stroke-width: 0.5; }
.PredefinedType-LINEWORK.strong { stroke-width: 1; }
.PredefinedType-BACKGROUND { stroke: black; stroke-width: 0.18; }
.PredefinedType-GRID { marker-start: url(#grid-marker); marker-end: url(#grid-marker); }
.PredefinedType-SECTION { stroke-dasharray: 12.5, 3, 3, 3; }
@@ -40,7 +46,6 @@ text, tspan { /* 2.5mm */ fill: black; stroke: none; font-family: 'OpenGost Type
.PredefinedType-SLOPEPERCENT { marker-end: url(#radius-marker-end); }
.PredefinedType-SLOPEFRACTION { marker-end: url(#radius-marker-end); }
.PredefinedType-DIAMETER { marker-start: url(#diameter-marker-start); marker-end: url(#diameter-marker-end); }
.PredefinedType-HIDDENLINE { stroke-dasharray: 3, 2; }
.PredefinedType-STAIRARROW { marker-start: url(#stair-marker-start); marker-end: url(#stair-marker-end); }
.PredefinedType-BOUNDARY { fill: none; stroke: red; stroke-width: 1; stroke-dasharray: 12, 4, 3, 4, 3, 4; }
.PredefinedType-SEALANT { fill: url(#crosshatch1); stroke-width: 0.25; }
@@ -75,7 +75,7 @@
<marker id="stair-marker-end" markerHeight="42" markerWidth="21" orient="auto" refX="21" refY="21">
<path d="M 0 0 L 21 21 L 0 42" class="annotation stair" style="stroke-width:2; marker-start: none; marker-end: none;" />
</marker>
<marker id="plan-level-marker" markerHeight="30" markerWidth="30" refX="15" refY="15">
<marker id="plan-level-marker" markerHeight="30" markerWidth="30" refX="15" refY="15" orient="auto">
<g>
<path d="M 15 0 L 15 30" class="annotation" style="stroke-width:1;" />
<path d="M 0 15 L 30 15" class="annotation" style="stroke-width:1;" />
@@ -84,7 +84,7 @@
<path d="M 15 15 L 22.5 15 A 7.5 7.5 0 0 1 15 22.5 Z" fill="black"/>
</g>
</marker>
<marker id="section-level-marker" markerHeight="11" markerWidth="90" refX="20" refY="10">
<marker id="section-level-marker" markerHeight="11" markerWidth="90" refX="20" refY="10" orient="auto-start-reverse">
<g>
<path d="M 0 1 L 20 1 L 10 11 Z" fill="black" />
<path d="M 0 1 L 90 1" class="annotation" style="stroke-width:1;" />

Before

Width:  |  Height:  |  Size: 5.6 KiB

After

Width:  |  Height:  |  Size: 5.7 KiB

@@ -389,7 +389,7 @@
"scene.display.shading.studiolight_background_blur": 0.0,
"scene.display.shading.studiolight_intensity": 0.0,
"scene.display.shading.studiolight_rotate_z": 0.0,
"scene.display.shading.type": "SOLID",
"scene.display.shading.type": "RENDERED",
"scene.display.shading.use_compositor": "DISABLED",
"scene.display.shading.use_dof": false,
"scene.display.shading.use_scene_lights": false,
@@ -640,7 +640,7 @@
"scene.display.shading.studiolight_background_blur": 0.0,
"scene.display.shading.studiolight_intensity": 0.0,
"scene.display.shading.studiolight_rotate_z": 0.0,
"scene.display.shading.type": "SOLID",
"scene.display.shading.type": "RENDERED",
"scene.display.shading.use_compositor": "DISABLED",
"scene.display.shading.use_dof": false,
"scene.display.shading.use_scene_lights": false,
@@ -752,4 +752,4 @@
"space.overlay.xray_alpha_bone": 0.0
}
}
}
}
@@ -21,6 +21,9 @@
<line x1="-5" x2="5" style="stroke: black; stroke-width: 0.25;" />
<text y="2.5" class="regular" text-anchor="middle" dominant-baseline="middle" data-type="text-template"></text>
</g>
<g id="window-tag">
<path style="fill: white; stroke: black; stroke-width: 0.25;" d="m 0.0161133,-6.0583333 5.2339915,3.8366023 -2.031433,6.1633977 c 0,0 -6.4894866,-0.027414 -6.4894866,-0.027414 l -1.97929,-6.1803395 5.2662181,-3.7922467" />
</g>
<g id="space-tag">
<text y="-5" class="large" text-anchor="middle" dominant-baseline="middle" data-type="text-template"></text>
<rect x="-6" y="-2.5" width="12" height="5" fill="white" stroke="black" style="stroke-width: 0.25;" />
@@ -41,4 +44,17 @@
<circle r="5" fill="white" stroke="black" style="stroke-width: 0.25;" />
<line x1="-5" y1="0" x2="5" y2="0" style="stroke: black; stroke-width: 0.25;" />
</g>
<g id="SurveyArea">
<circle r="1" fill="black" stroke="black" style="stroke-width: 0.25;" />
</g>
<g id="SurveyArea-CONTROLPOINT">
<path style="fill: white; stroke: black; stroke-width: 0.25;" d="M 2.286165,1.4798666 H -1.0000316e-7 -2.2861651 L -1.1430826,-0.5000101 -1.0000316e-7,-2.479888 1.1430827,-0.5000101 Z" />
<circle r="0.5" fill="black" stroke="black" style="stroke-width: 0.25;" />
</g>
<g id="SurveyArea-TRAVERSEPOINT">
<path style="fill: white; stroke: black; stroke-width: 0.5;" d="M -2,-2 2,2 M -2,2 2,-2" />
</g>
<g id="SurveyArea-SPOTELEVATION">
<path style="fill: white; stroke: black; stroke-width: 0.5;" d="M 0,-2 0,2 M -2,0 2,0" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,16 @@
ISO-10303-21;
HEADER;
FILE_DESCRIPTION((),'2;1');
FILE_NAME('EQto_BodyGeometryValidation.ifc','2020-01-01T00:00:00',(),(),'EQto_BodyGeometryValidation','EQto_BodyGeometryValidation',$);
FILE_SCHEMA(('IFC4'));
ENDSEC;
DATA;
#1=IFCPROPERTYSETTEMPLATE('2KS9su6r517uLXTGKwwDdn',$,'EQto_BodyGeometryValidation','Quantities supplied for validating the correct interpretation of the body shape representation at import. In case of multiple representation items, the quantities are summed for each of the items (irrespective of any overlap). Choosing a suitable tolerance value for comparing the supplied numbers to the numbers calculated from the reconstructed geometry is at the discretion of the importing application.',.QTO_OCCURRENCEDRIVEN.,'IfcProduct',(#2,#3,#4,#5,#6,#7));
#2=IFCSIMPLEPROPERTYTEMPLATE('3iCdOOLRjCwQ_HRTB41Xh8',$,'GrossSurfaceArea','Total gross area of the object, normally generated as perimeter * length + 2 * cross section area. It is the sum of OuterSurfaceArea + (2 x CrossSectionArea) and shall only be given, if the OuterSurfaceArea and CrossSectionArea cannot be established separately.\X2\000A000A\X0\Total gross surface area of the element before applying product-level geometric features such as openings and projections.',.Q_AREA.,$,$,$,$,$,$,.READWRITE.);
#3=IFCSIMPLEPROPERTYTEMPLATE('0juemEqF5889vg7HwR87Fv',$,'NetSurfaceArea','Net surface area of the object, normally generated as perimeter * length + 2 * cross section area taking into account possible processing features (cut-out''s, etc.) or openings and recesses.\X2\000A000A\X0\Total net surface area of the element after applying product-level geometric features such as openings and projections.',.Q_AREA.,$,$,$,$,$,$,.READWRITE.);
#4=IFCSIMPLEPROPERTYTEMPLATE('1LUuLSnMXFDgGLBCvte8aH',$,'GrossVolume','Total gross volume of the object. Openings, recesses, enclosed objects and projections are not taken into account.\X2\000A000A\X0\Total gross volume of the element before applying product-level geometric features such as openings and projections.',.Q_VOLUME.,$,$,$,$,$,$,.READWRITE.);
#5=IFCSIMPLEPROPERTYTEMPLATE('0ny7TXUFz8wvnoQ2eU8czF',$,'NetVolume','Total net volume of the object, taking into account possible processing features (cut-out''s, etc.) or openings and recesses.\X2\000A000A\X0\Total net volume of the element before applying product-level geometric features such as openings and projections.',.Q_VOLUME.,$,$,$,$,$,$,.READWRITE.);
#6=IFCSIMPLEPROPERTYTEMPLATE('31g8_gdUv8KRkOWzW48KoP',$,'SurfaceGenusBeforeFeatures','The Surface Genus of the evaluated representation items before applying product-level geometric features such as openings and projections.Surface Genus is a topological measure that represents the number of "holes" or "handles" on a surface. For example, a sphere has genus 0, and a torus has genus 1.Computed using the Euler characteristic:$$\\chi=V-E+F$$With the numbers of vertices (V), edges (E) and faces (F)$$\\chi=2\X2\2212\X0\2g\X2\2212\X0\b$$With surface genus (g) and the number of boundaries (b) the latter zero in case of an enclosed volume.',.Q_COUNT.,$,$,$,$,$,$,.READWRITE.);
#7=IFCSIMPLEPROPERTYTEMPLATE('3kBBtGUebC09mUggGTKpjj',$,'SurfaceGenusAfterFeatures','The Surface Genus of the evaluated representation items after applying product-level geometric features such as openings and projections.Surface Genus is a topological measure that represents the number of "holes" or "handles" on a surface. For example, a sphere has genus 0, and a torus has genus 1.Computed using the Euler characteristic:$$\\chi=V-E+F$$With the numbers of vertices (V), edges (E) and faces (F)$$\\chi=2\X2\2212\X0\2g\X2\2212\X0\b$$With surface genus (g) and the number of boundaries (b) the latter zero in case of an enclosed volume.',.Q_COUNT.,$,$,$,$,$,$,.READWRITE.);
ENDSEC;
END-ISO-10303-21;
@@ -1,214 +0,0 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2021 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/>.
presets = {
"Basic": ["project", "search", "bcf", "attribute", "spatial", "pset", "qto"],
"Admin": [
"project",
"search",
"bcf",
"root",
"unit",
"model",
"georeference",
"context",
"drawing",
"misc",
"attribute",
"type",
"spatial",
"void",
"aggregate",
"geometry",
"cobie",
"resource",
"cost",
"sequence",
"group",
"system",
"structural",
"boundary",
"profile",
"material",
"style",
"layer",
"owner",
"pset",
"qto",
"classification",
"constraint",
"document",
"pset_template",
"clash",
"lca",
"csv",
"bimtester",
"diff",
"patch",
"covetool",
"augin",
"debug",
],
"BIM Coordination": [
"project",
"search",
"bcf",
"root",
"unit",
"georeference",
"attribute",
"type",
"spatial",
"void",
"aggregate",
"owner",
"pset",
"qto",
"clash",
"csv",
"bimtester",
"diff",
"patch",
"debug",
],
"Architecture": [
"project",
"search",
"bcf",
"root",
"unit",
"model",
"georeference",
"context",
"drawing",
"misc",
"attribute",
"type",
"spatial",
"void",
"aggregate",
"geometry",
"group",
"system",
"boundary",
"profile",
"material",
"style",
"layer",
"owner",
"pset",
"qto",
"classification",
"pset_template",
"clash",
"csv",
],
"Structural Engineering": [
"project",
"search",
"bcf",
"root",
"unit",
"model",
"georeference",
"context",
"drawing",
"misc",
"attribute",
"type",
"spatial",
"void",
"aggregate",
"geometry",
"group",
"system",
"structural",
"boundary",
"profile",
"material",
"style",
"layer",
"owner",
"pset",
"qto",
"classification",
"pset_template",
"clash",
"csv",
],
"MEP": [
"project",
"search",
"bcf",
"root",
"unit",
"model",
"georeference",
"context",
"drawing",
"misc",
"attribute",
"type",
"spatial",
"void",
"aggregate",
"geometry",
"group",
"system",
"boundary",
"profile",
"material",
"style",
"layer",
"owner",
"pset",
"qto",
"classification",
"pset_template",
"clash",
"csv",
],
"Quantity Surveying": ["project", "search", "attribute", "spatial", "void", "pset", "qto", "classification", "csv"],
"Model Enrichment": [
"project",
"search",
"root",
"attribute",
"spatial",
"pset",
"qto",
"pset_template",
"csv",
"patch",
],
"3D Visualisation": ["project", "search", "bcf", "attribute", "spatial", "pset", "qto"],
"4D Scheduling": ["project", "search", "attribute", "spatial", "resource", "cost", "sequence", "pset", "qto"],
"5D Cost Management": [
"project",
"search",
"attribute",
"spatial",
"resource",
"cost",
"sequence",
"pset",
"qto",
"lca",
],
"Facility Management": ["project", "search", "attribute", "spatial", "cobie", "pset", "qto", "lca"],
}
@@ -1,2 +0,0 @@
Name,foo
TransomThickness,500
1 Name foo
2 TransomThickness 500
Binary file not shown.
+121 -26
View File
@@ -16,6 +16,7 @@
# 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 os
import bpy
import json
import addon_utils
@@ -30,6 +31,7 @@ from mathutils import Vector
from math import cos, degrees
cwd = os.path.dirname(os.path.realpath(__file__))
global_subscription_owner = object()
@@ -52,10 +54,12 @@ def mode_callback(obj, data):
def name_callback(obj, data):
# TODO Do we still need this, now that we are monitoring the undo redo objects?
try:
obj.name
except:
# The object is invalid but somehow still has a callback. Clear all
# msgbus subscriptions to prevent useless further triggers.
bpy.msgbus.clear_by_owner(obj)
return # In case the object RNA is gone during an undo / redo operation
# Blender names are up to 63 UTF-8 bytes
if len(bytes(obj.name, "utf-8")) >= 63:
@@ -72,6 +76,9 @@ def name_callback(obj, data):
if not obj.BIMObjectProperties.ifc_definition_id or "/" not in obj.name:
return
element = IfcStore.get_file().by_id(obj.BIMObjectProperties.ifc_definition_id)
if element.is_a("IfcGridAxis"):
element.AxisTag = obj.name.split("/")[1]
refresh_ui_data()
if not element.is_a("IfcRoot"):
return
if obj.BIMObjectProperties.collection:
@@ -98,8 +105,8 @@ def update_bim_tool_props():
if not obj:
return
mode = bpy.context.mode
current_tool = bpy.context.workspace.tools.from_space_view3d_mode(mode).idname
if current_tool != "bim.bim_tool":
current_tool = bpy.context.workspace.tools.from_space_view3d_mode(mode)
if not current_tool or current_tool.idname != "bim.bim_tool":
return
element = tool.Ifc.get_entity(obj)
if not element:
@@ -168,6 +175,9 @@ def refresh_ui_data():
except AttributeError:
pass
if isinstance(tool.Ifc.get(), ifcopenshell.sqlite):
tool.Ifc.get().clear_cache()
def purge_module_data():
from blenderbim.bim import modules
@@ -195,40 +205,22 @@ def loadIfcStore(scene):
IfcStore.relink_all_objects()
@persistent
def undo_pre(scene):
IfcStore.track_undo_redo_stack_object_map()
@persistent
def undo_post(scene):
if IfcStore.last_transaction != bpy.context.scene.BIMProperties.last_transaction:
IfcStore.last_transaction = bpy.context.scene.BIMProperties.last_transaction
IfcStore.undo()
IfcStore.undo(until_key=bpy.context.scene.BIMProperties.last_transaction)
purge_module_data()
IfcStore.track_undo_redo_stack_selected_objects()
IfcStore.reload_undo_redo_stack_objects()
@persistent
def redo_pre(scene):
IfcStore.track_undo_redo_stack_object_map()
tool.Ifc.rebuild_element_maps()
@persistent
def redo_post(scene):
if IfcStore.last_transaction != bpy.context.scene.BIMProperties.last_transaction:
IfcStore.last_transaction = bpy.context.scene.BIMProperties.last_transaction
IfcStore.redo()
IfcStore.redo(until_key=bpy.context.scene.BIMProperties.last_transaction)
purge_module_data()
IfcStore.track_undo_redo_stack_selected_objects()
IfcStore.reload_undo_redo_stack_objects()
@persistent
def ensureIfcExported(scene):
if IfcStore.get_file() and not bpy.context.scene.BIMProperties.ifc_file:
bpy.ops.export_ifc.bim("INVOKE_DEFAULT")
tool.Ifc.rebuild_element_maps()
def get_application(ifc):
@@ -265,8 +257,78 @@ def viewport_shading_changed_callback(area):
bpy.context.scene.BIMStylesProperties.active_style_type = "External"
if getattr(bpy.types, "SCENE_PT_scene"):
class Override_SCENE_PT_scene(bpy.types.SCENE_PT_scene):
bl_idname = "SCENE_PT_scene_override"
@classmethod
def poll(cls, context):
return tool.Blender.is_tab(context, "BLENDER")
if getattr(bpy.types, "SCENE_PT_unit"):
class Override_SCENE_PT_unit(bpy.types.SCENE_PT_unit):
bl_idname = "SCENE_PT_unit_override"
@classmethod
def poll(cls, context):
return tool.Blender.is_tab(context, "BLENDER")
if getattr(bpy.types, "SCENE_PT_physics"):
class Override_SCENE_PT_physics(bpy.types.SCENE_PT_physics):
bl_idname = "SCENE_PT_physics_override"
@classmethod
def poll(cls, context):
return tool.Blender.is_tab(context, "BLENDER")
if getattr(bpy.types, "SCENE_PT_rigid_body_world"):
class Override_SCENE_PT_rigid_body_world(bpy.types.SCENE_PT_rigid_body_world):
bl_idname = "SCENE_PT_rigid_body_world_override"
@classmethod
def poll(cls, context):
return tool.Blender.is_tab(context, "BLENDER")
if getattr(bpy.types, "SCENE_PT_audio"):
class Override_SCENE_PT_audio(bpy.types.SCENE_PT_audio):
bl_idname = "SCENE_PT_audio_override"
@classmethod
def poll(cls, context):
return tool.Blender.is_tab(context, "BLENDER")
if getattr(bpy.types, "SCENE_PT_keying_sets"):
class Override_SCENE_PT_keying_sets(bpy.types.SCENE_PT_keying_sets):
bl_idname = "SCENE_PT_keying_sets_override"
@classmethod
def poll(cls, context):
return tool.Blender.is_tab(context, "BLENDER")
if getattr(bpy.types, "SCENE_PT_custom_props"):
class Override_SCENE_PT_custom_props(bpy.types.SCENE_PT_custom_props):
bl_idname = "SCENE_PT_custom_props_override"
@classmethod
def poll(cls, context):
return tool.Blender.is_tab(context, "BLENDER")
@persistent
def setDefaultProperties(scene):
def load_post(scene):
global global_subscription_owner
active_object_key = bpy.types.LayerObjects, "active"
bpy.msgbus.subscribe_rna(
@@ -290,3 +352,36 @@ def setDefaultProperties(scene):
ifcopenshell.api.owner.settings.get_user = lambda ifc: core_owner.get_user(tool.Owner)
ifcopenshell.api.owner.settings.get_application = get_application
AuthoringData.type_thumbnails = {}
if bpy.context.preferences.addons["blenderbim"].preferences.should_setup_workspace:
if "BIM" in bpy.data.workspaces:
bpy.context.window.workspace = bpy.data.workspaces["BIM"]
else:
bpy.ops.workspace.append_activate(idname="BIM", filepath=os.path.join(cwd, "data", "workspace.blend"))
# To improve usability for new users, we hijack the scene properties
# tab. We override default scene properties panels with our own poll
# to hide them unless the user has chosen to view Blender properties.
for panel in [
"SCENE_PT_scene",
"SCENE_PT_unit",
"SCENE_PT_physics",
"SCENE_PT_rigid_body_world",
"SCENE_PT_audio",
"SCENE_PT_keying_sets",
"SCENE_PT_custom_props",
]:
if getattr(bpy.types, panel, None):
try:
bpy.utils.register_class(globals()[f"Override_{panel}"])
bpy.utils.unregister_class(getattr(bpy.types, panel))
except:
pass
# https://blender.stackexchange.com/questions/140644/how-can-make-the-state-of-a-boolean-property-relative-to-the-3d-view-area
for screen in bpy.data.screens:
if len(screen.BIMAreaProperties) == 20:
continue
screen.BIMAreaProperties.clear()
for i in range(20): # 20 is an arbitrary value of split areas
screen.BIMAreaProperties.add()
+5
View File
@@ -273,6 +273,9 @@ def convert_property_group_from_si(property_group, skip_props=()):
setattr(property_group, prop_name, prop_value)
# TODO this should move into ifcopenshell.util
class IfcHeaderExtractor:
def __init__(self, filepath: str):
self.filepath = filepath
@@ -284,6 +287,8 @@ class IfcHeaderExtractor:
return self.extract_ifc_spf(ifc_file)
elif extension.lower() == "ifczip":
return self.extract_ifc_zip()
elif extension.lower() == "ifcsqlite":
return {} # TODO
def extract_ifc_spf(self, ifc_file):
# https://www.steptools.com/stds/step/IS_final_p21e3.html#clause-8
+58 -117
View File
@@ -26,6 +26,7 @@ import ifcopenshell
import blenderbim.bim.handler
import blenderbim.tool as tool
from pathlib import Path
from blenderbim.tool.brick import BrickStore
class IfcStore:
@@ -43,9 +44,6 @@ class IfcStore:
classification_file = None
library_path = ""
library_file = None
element_listeners = set()
undo_redo_stack_objects = set()
undo_redo_stack_object_names = {}
current_transaction = ""
last_transaction = ""
history = []
@@ -120,6 +118,8 @@ class IfcStore:
@staticmethod
def load_file(path):
if not os.path.isfile(path):
return
extension = path.split(".")[-1]
if extension.lower() == "ifczip":
with tempfile.TemporaryDirectory() as unzipped_path:
@@ -130,7 +130,9 @@ class IfcStore:
return
elif extension.lower() == "ifcxml":
IfcStore.file = ifcopenshell.file(ifcopenshell.ifcopenshell_wrapper.parse_ifcxml(path))
elif extension.lower() == "ifc":
elif bpy.context.scene.BIMProjectProperties.should_stream:
IfcStore.file = ifcopenshell.open(path, should_stream=True)
else:
IfcStore.file = ifcopenshell.open(path)
@staticmethod
@@ -156,96 +158,6 @@ class IfcStore:
return
return obj
@staticmethod
def add_element_listener(callback):
IfcStore.element_listeners.add(callback)
@staticmethod
def track_undo_redo_stack_object_map():
"""Keeps track of currently mapped object names, typically during undo and redo
When any Blender object is stored outside a Blender PointerProperty, such as
in a regular Python list, there is the likely probability that the object
will be invalidated when undo or redo occurs. Object invalidation seems to
occur whenever an object is affected during an operation.
For example, if an operator deletes a modifier on o1, then o1 will be invalidated.
"""
for key, value in IfcStore.id_map.items():
try:
IfcStore.undo_redo_stack_object_names[key] = value.name
except:
continue
@staticmethod
def track_undo_redo_stack_selected_objects():
"""Keeps track of selected object names, typically during undo and redo
When any Blender object is stored outside a Blender PointerProperty, such as
in a regular Python list, there is the likely probability that the object
will be invalidated when undo or redo occurs. Object invalidation seems to
occur for selected objects either pre/post undo/redo event, including
selected objects for consecutive undo/redos, and all children. This is
important because selected objects are often deleted from the scene.
So if I first select o1, then o2, then o3, then press undo, o3 will be
invalidated. If instead I press undo twice, o3 and o2 will be invalidated.
"""
if bpy.context.active_object:
objects = set([o.name for o in bpy.context.selected_objects + [bpy.context.active_object]])
objects.update([o.name for o in bpy.context.active_object.children])
else:
objects = set([o.name for o in bpy.context.selected_objects])
for obj in bpy.context.selected_objects:
objects.update([o.name for o in obj.children])
IfcStore.undo_redo_stack_objects |= objects
@staticmethod
def reload_undo_redo_stack_objects():
"""Reloads any invalidated objects after undo or redo
After an undo or redo operation, objects may have been invalidated in
our id_map and guid_map. Invalidated objects are typically those that
have been manipulated or deleted. This checks the cache of mapped and
selected objects prior to the operation and ensures that if the object
is invalidated, they are reloaded based on the object name that was
tracked prior to the undo / redo.
"""
file = IfcStore.get_file()
if not file:
return
# First, reload objects that were selected or active
for name in IfcStore.undo_redo_stack_objects:
obj = bpy.data.objects.get(name)
if not obj:
continue
if not obj.BIMObjectProperties.ifc_definition_id:
continue
element = file.by_id(obj.BIMObjectProperties.ifc_definition_id)
data = {"id": element.id(), "obj": obj.name}
if hasattr(element, "GlobalId"):
data["guid"] = element.GlobalId
IfcStore.commit_link_element(data)
# Scan for any straggling invalidated objects which were indirectly affected and reload them too.
for key, value in IfcStore.id_map.items():
try:
value.name
except:
# 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)
data = {"id": element.id(), "obj": obj.name}
if hasattr(element, "GlobalId"):
data["guid"] = element.GlobalId
IfcStore.commit_link_element(data)
@staticmethod
def relink_all_objects():
if not IfcStore.get_file():
@@ -278,6 +190,13 @@ class IfcStore:
@staticmethod
def link_element(element, obj):
# Please use tool.Ifc.link() instead of this method. We want to
# refactor this class and deprecate usage of IfcStore in favour of
# tools.
if not isinstance(obj, (bpy.types.Object, bpy.types.Material)):
obj.BIMMeshProperties.ifc_definition_id = element.id()
return
existing_obj = IfcStore.id_map.get(element.id(), None)
if existing_obj == obj:
return
@@ -304,9 +223,6 @@ class IfcStore:
blenderbim.bim.handler.subscribe_to(obj, "mode", blenderbim.bim.handler.mode_callback)
blenderbim.bim.handler.subscribe_to(obj, "active_material_index", blenderbim.bim.handler.active_material_index_callback)
for listener in IfcStore.element_listeners:
listener(element, obj)
if IfcStore.history:
data = {"id": element.id(), "guid": getattr(element, "GlobalId", None), "obj": obj.name}
IfcStore.history[-1]["operations"].append(
@@ -327,10 +243,12 @@ class IfcStore:
IfcStore.id_map[data["id"]] = obj
if "guid" in data:
IfcStore.guid_map[data["guid"]] = obj
blenderbim.bim.handler.subscribe_to(obj, "mode", blenderbim.bim.handler.mode_callback)
blenderbim.bim.handler.subscribe_to(obj, "name", blenderbim.bim.handler.name_callback)
if isinstance(obj, bpy.types.Material):
blenderbim.bim.handler.subscribe_to(obj, "diffuse_color", blenderbim.bim.handler.color_callback)
elif isinstance(obj, bpy.types.Object):
blenderbim.bim.handler.subscribe_to(obj, "mode", blenderbim.bim.handler.mode_callback)
blenderbim.bim.handler.subscribe_to(obj, "active_material_index", blenderbim.bim.handler.active_material_index_callback)
# 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.
@@ -396,24 +314,33 @@ class IfcStore:
)
@staticmethod
def execute_ifc_operator(operator, context):
def execute_ifc_operator(operator, context, is_invoke=False):
is_top_level_operator = not bool(IfcStore.current_transaction)
if is_top_level_operator:
IfcStore.begin_transaction(operator)
IfcStore.get_file().begin_transaction()
if tool.Ifc.get():
tool.Ifc.get().begin_transaction()
if BrickStore.graph:
BrickStore.begin_transaction()
# This empty transaction ensures that each operator has at least one transaction
IfcStore.add_transaction_operation(operator, rollback=lambda data: True, commit=lambda data: True)
else:
operator.transaction_key = IfcStore.current_transaction
result = getattr(operator, "_execute")(context)
if is_invoke:
result = getattr(operator, "_invoke")(context, None)
else:
result = getattr(operator, "_execute")(context)
if is_top_level_operator:
IfcStore.get_file().end_transaction()
IfcStore.add_transaction_operation(
operator, rollback=lambda d: IfcStore.get_file().undo(), commit=lambda d: IfcStore.get_file().redo()
)
if tool.Ifc.get():
tool.Ifc.get().end_transaction()
IfcStore.add_transaction_operation(
operator, rollback=lambda d: tool.Ifc.get().undo(), commit=lambda d: tool.Ifc.get().redo()
)
if BrickStore.graph:
BrickStore.end_transaction()
IfcStore.end_transaction(operator)
blenderbim.bim.handler.refresh_ui_data()
@@ -421,8 +348,6 @@ class IfcStore:
@staticmethod
def begin_transaction(operator):
IfcStore.undo_redo_stack_objects = set()
IfcStore.undo_redo_stack_object_names = {}
IfcStore.current_transaction = str(uuid.uuid4())
operator.transaction_key = IfcStore.current_transaction
@@ -448,19 +373,35 @@ class IfcStore:
IfcStore.future = []
@staticmethod
def undo():
def undo(until_key=None):
BrickStore.undo()
if not IfcStore.history:
return
event = IfcStore.history.pop()
for transaction in event["operations"][::-1]:
transaction["rollback"](transaction["data"])
IfcStore.future.append(event)
while IfcStore.history:
if IfcStore.history[-1]["key"] == until_key:
return
event = IfcStore.history.pop()
for transaction in event["operations"][::-1]:
transaction["rollback"](transaction["data"])
IfcStore.future.append(event)
@staticmethod
def redo():
def redo(until_key=None):
BrickStore.redo()
if not IfcStore.future:
return
event = IfcStore.future.pop()
for transaction in event["operations"]:
transaction["commit"](transaction["data"])
IfcStore.history.append(event)
has_encountered_key = False
while IfcStore.future:
if has_encountered_key and IfcStore.future[-1]["key"] != until_key:
return
elif IfcStore.future[-1]["key"] == until_key:
has_encountered_key = True
event = IfcStore.future.pop()
for transaction in event["operations"]:
transaction["commit"](transaction["data"])
IfcStore.history.append(event)
+180 -63
View File
@@ -52,6 +52,7 @@ class FileCopy(threading.Thread):
class MaterialCreator:
def __init__(self, ifc_import_settings, ifc_importer):
self.mesh = None
self.obj = None
self.materials = {}
self.styles = {}
self.parsed_meshes = set()
@@ -60,6 +61,8 @@ class MaterialCreator:
def create(self, element, obj, mesh):
self.mesh = mesh
# as ifcopenshell triangulates the mesh, we need to merge it to quads again
self.obj = obj
if (hasattr(element, "Representation") and not element.Representation) or (
hasattr(element, "RepresentationMaps") and not element.RepresentationMaps
):
@@ -132,21 +135,67 @@ class MaterialCreator:
return
for style_id in style_ids:
material = self.styles[style_id]
ifc_coordinate_id = material.BIMMaterialProperties.ifc_coordinate_id
if ifc_coordinate_id != 0:
self.load_texture_map(tool.Ifc.get().by_id(ifc_coordinate_id))
if self.mesh.materials.find(material.name) == -1:
self.mesh.materials.append(material)
return True
def load_texture_map(self, coordinates):
# Get a BMesh representation
bm = bmesh.new()
bm.from_mesh(self.mesh)
uv_layer = bm.loops.layers.uv.verify()
# remap the faceset CoordList index to the vertices in blender mesh
coordinates_remap = []
for co in coordinates.MappedTo.Coordinates.CoordList:
co = mathutils.Vector(co)
index = next(v.index for v in bm.verts if (v.co - co).length_squared < 1e-5)
coordinates_remap.append(index)
faces_remap = None
texture_map = None
if coordinates.is_a("IfcIndexedPolygonalTextureMap"):
faces_remap = [[coordinates_remap[i-1] for i in tex_coord_index.TexCoordsOf.CoordIndex]
for tex_coord_index in coordinates.TexCoordIndices]
texture_map = [tex_coord_index.TexCoordIndex for tex_coord_index in coordinates.TexCoordIndices]
elif coordinates.is_a("IfcIndexedTriangleTextureMap"):
faces_remap = [[coordinates_remap[i-1] for i in triangle_face]
for triangle_face in coordinates.MappedTo.CoordIndex]
texture_map = coordinates.TexCoordIndex
# apply uv to each face
for bface in bm.faces:
face = [loop.vert.index for loop in bface.loops]
# find the corresponding TexCoordIndex by matching ifc faceset with blender face
# remap TexCoordIndex as the loop start may different from blender face
texCoordIndex = next(
[tex_coord_index[face_remap.index(i)] for i in face]
for tex_coord_index, face_remap in zip(texture_map, faces_remap)
if all(i in face_remap for i in face)
)
# apply uv to each loop
for loop, i in zip(bface.loops, texCoordIndex):
loop[uv_layer].uv = coordinates.TexCoords.TexCoordsList[i-1]
# Finish up, write the bmesh back to the mesh
bm.to_mesh(self.mesh)
bm.free()
def assign_material_slots_to_faces(self):
if "ios_materials" not in self.mesh or not self.mesh["ios_materials"]:
return
if len(self.mesh.materials) == 1:
return
material_to_slot = {}
for i, material in enumerate(self.mesh["ios_materials"]):
slot_index = self.mesh.materials.find(self.styles[material].name)
material_to_slot[i] = slot_index
if len(self.mesh.polygons) == len(self.mesh["ios_material_ids"]):
for i, material in enumerate(self.mesh["ios_materials"]):
slot_index = self.mesh.materials.find(self.styles[material].name)
material_to_slot[i] = slot_index
material_index = [
(material_to_slot[mat_id] if mat_id != -1 else 0) for mat_id in self.mesh["ios_material_ids"]
]
@@ -327,13 +376,19 @@ class IfcImporter:
if isinstance(self.elements, set):
self.elements = list(self.elements)
# TODO: enable filtering for annotations
self.annotations = set([a for a in self.file.by_type("IfcAnnotation") if not a.HasAssignments])
else:
if self.file.schema in ("IFC2X3", "IFC4"):
self.elements = self.file.by_type("IfcElement") + self.file.by_type("IfcProxy")
else:
self.elements = self.file.by_type("IfcElement")
self.annotations = set([a for a in self.file.by_type("IfcAnnotation") if not a.HasAssignments])
drawing_groups = [g for g in self.file.by_type("IfcGroup") if g.ObjectType == "DRAWING"]
drawing_annotations = set()
for drawing_group in drawing_groups:
for rel in drawing_group.IsGroupedBy:
drawing_annotations.update(rel.RelatedObjects)
self.annotations = set([a for a in self.file.by_type("IfcAnnotation")])
self.annotations -= drawing_annotations
self.elements = [e for e in self.elements if not e.is_a("IfcFeatureElement")]
if self.ifc_import_settings.is_coordinating:
@@ -362,11 +417,13 @@ class IfcImporter:
while True:
results.add(spatial_element)
spatial_element = ifcopenshell.util.element.get_aggregate(spatial_element)
if not spatial_element or spatial_element.is_a() in ("IfcContext", "IfcProject"):
if not spatial_element or spatial_element.is_a() in ("IfcProject", "IfcProjectLibrary"):
break
return results
def parse_native_elements(self):
if not self.ifc_import_settings.should_load_geometry:
return
for element in self.elements:
if self.is_native(element):
self.native_elements.add(element)
@@ -414,7 +471,7 @@ class IfcImporter:
for representation in representations:
items = representation["raw"].Items or [] # Be forgiving of invalid IFCs because Revit :(
if len(items) == 1 and items[0].is_a("IfcSweptDiskSolid"):
if tool.Pset.get_element_pset(element, "BBIM_Railing"):
if tool.Blender.Modifier.is_railing(element):
return False
return True
elif len(items) and ( # See #2508 why we accommodate for invalid IFCs here
@@ -422,7 +479,7 @@ class IfcImporter:
and len({i.is_a() for i in items}) == 1
and len({i.Radius for i in items}) == 1
):
if tool.Pset.get_element_pset(element, "BBIM_Railing"):
if tool.Blender.Modifier.is_railing(element):
return False
return True
return False
@@ -578,6 +635,8 @@ class IfcImporter:
return result
def create_grids(self):
if not self.ifc_import_settings.should_load_geometry:
return
grids = self.file.by_type("IfcGrid")
for grid in grids:
shape = None
@@ -623,34 +682,37 @@ class IfcImporter:
def create_element_type(self, element):
self.ifc_import_settings.logger.info("Creating object %s", element)
representation = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
if not representation:
representation = ifcopenshell.util.representation.get_representation(element, "Plan", "Annotation")
if not representation:
representation = ifcopenshell.util.representation.get_representation(element, "Model", "Annotation")
mesh = None
if representation:
mesh_name = "{}/{}".format(representation.ContextOfItems.id(), representation.id())
mesh = self.meshes.get(mesh_name)
if mesh is None:
shape = None
try:
shape = ifcopenshell.geom.create_shape(self.settings, representation)
except:
if self.ifc_import_settings.should_load_geometry:
representation = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
if not representation:
representation = ifcopenshell.util.representation.get_representation(element, "Plan", "Annotation")
if not representation:
representation = ifcopenshell.util.representation.get_representation(element, "Model", "Annotation")
if representation:
mesh_name = "{}/{}".format(representation.ContextOfItems.id(), representation.id())
mesh = self.meshes.get(mesh_name)
if mesh is None:
shape = None
try:
shape = ifcopenshell.geom.create_shape(self.settings_2d, representation)
shape = ifcopenshell.geom.create_shape(self.settings, representation)
except:
self.ifc_import_settings.logger.error("Failed to generate shape for %s", element)
if shape:
mesh = self.create_mesh(element, shape)
tool.Loader.link_mesh(shape, mesh)
self.meshes[mesh_name] = mesh
try:
shape = ifcopenshell.geom.create_shape(self.settings_2d, representation)
except:
self.ifc_import_settings.logger.error("Failed to generate shape for %s", element)
if shape:
mesh = self.create_mesh(element, shape)
tool.Loader.link_mesh(shape, mesh)
self.meshes[mesh_name] = mesh
obj = bpy.data.objects.new(tool.Loader.get_name(element), mesh)
self.link_element(element, obj)
self.material_creator.create(element, obj, mesh)
self.type_products[element.GlobalId] = obj
def create_native_elements(self):
if not self.ifc_import_settings.should_load_geometry:
return
progress = 0
checkpoint = time.time()
total = len(self.native_elements)
@@ -679,7 +741,7 @@ class IfcImporter:
mesh = self.create_native_faceted_brep(element, mesh_name)
elif native_data["type"] == "IfcFaceBasedSurfaceModel":
mesh = self.create_native_faceted_brep(element, mesh_name)
mesh.BIMMeshProperties.ifc_definition_id = representation.id()
tool.Ifc.link(representation, mesh)
mesh.name = mesh_name
self.meshes[mesh_name] = mesh
self.create_product(element, mesh=mesh)
@@ -692,21 +754,76 @@ class IfcImporter:
self.create_generic_elements(self.elements)
def create_generic_elements(self, elements):
if isinstance(self.file, ifcopenshell.sqlite):
return self.create_generic_sqlite_elements(elements)
# Based on my experience in viewing BIM models, representations are prioritised as follows:
# 1. 3D Body, 2. 2D Body, 3. 2D Plans / annotations, 4. Point clouds, 5. No representation
# If an element has a representation that doesn't follow 1, 2, 3, or 4, it will not show by default.
# The user can load them later if they want to view them.
products = self.create_products(elements)
elements -= products
products = self.create_products(elements, settings=self.settings_curve)
elements -= products
products = self.create_products(elements, settings=self.settings_2d)
elements -= products
products = self.create_pointclouds(elements)
elements -= products
for element in elements:
if self.ifc_import_settings.should_load_geometry:
products = self.create_products(elements)
elements -= products
products = self.create_products(elements, settings=self.settings_curve)
elements -= products
products = self.create_products(elements, settings=self.settings_2d)
elements -= products
products = self.create_pointclouds(elements)
elements -= products
total = len(elements)
for i, element in enumerate(elements):
if i % 250 == 0:
print("{} / {} elements processed ...".format(i, total))
self.create_product(element)
def create_generic_sqlite_elements(self, elements):
self.geometry_cache = self.file.get_geometry([e.id() for e in elements])
for geometry_id, geometry in self.geometry_cache["geometry"].items():
mesh_name = tool.Loader.get_mesh_name(type("Geometry", (), {"id": geometry_id}))
mesh = bpy.data.meshes.new(mesh_name)
verts = geometry["verts"]
mesh["has_cartesian_point_offset"] = False
if geometry["faces"]:
num_vertices = len(verts) // 3
total_faces = len(geometry["faces"])
loop_start = range(0, total_faces, 3)
num_loops = total_faces // 3
loop_total = [3] * num_loops
num_vertex_indices = len(geometry["faces"])
mesh.vertices.add(num_vertices)
mesh.vertices.foreach_set("co", verts)
mesh.loops.add(num_vertex_indices)
mesh.loops.foreach_set("vertex_index", geometry["faces"])
mesh.polygons.add(num_loops)
mesh.polygons.foreach_set("loop_start", loop_start)
mesh.polygons.foreach_set("loop_total", loop_total)
mesh.update()
else:
e = geometry["edges"]
v = verts
vertices = [[v[i], v[i + 1], v[i + 2]] for i in range(0, len(v), 3)]
edges = [[e[i], e[i + 1]] for i in range(0, len(e), 2)]
mesh.from_pydata(vertices, edges, [])
mesh["ios_materials"] = geometry["materials"]
mesh["ios_material_ids"] = geometry["material_ids"]
self.meshes[mesh_name] = mesh
total = len(elements)
for i, element in enumerate(elements):
if i % 250 == 0:
print("{} / {} elements processed ...".format(i, total))
mesh = None
geometry_id = self.geometry_cache["shapes"][element.id()]["geometry"]
if geometry_id:
mesh_name = tool.Loader.get_mesh_name(type("Geometry", (), {"id": geometry_id}))
mesh = self.meshes.get(mesh_name)
self.create_product(element, mesh=mesh)
def create_products(self, products, settings=None):
if settings is None:
settings = self.settings
@@ -824,7 +941,7 @@ class IfcImporter:
return None
for representation in representations:
if representation.RepresentationType == "PointCloud":
if representation.RepresentationType in ("PointCloud", "Point"):
return representation
elif self.file.schema == "IFC2X3" and representation.RepresentationType == "GeometricSet":
@@ -853,7 +970,7 @@ class IfcImporter:
return result
def create_pointcloud(self, product, representation):
placement_matrix = ifcopenshell.util.placement.get_local_placement(product.ObjectPlacement)
placement_matrix = self.get_element_matrix(product)
vertex_list = []
for item in representation.Items:
if item.is_a("IfcCartesianPointList"):
@@ -869,6 +986,7 @@ class IfcImporter:
mesh_name = f"{representation.ContextOfItems.id()}/{representation.id()}"
mesh = bpy.data.meshes.new(mesh_name)
mesh.from_pydata(vertex_list, [], [])
tool.Ifc.link(representation, mesh)
obj = bpy.data.objects.new("{}/{}".format(product.is_a(), product.Name), mesh)
self.set_matrix_world(obj, self.apply_blender_offset_to_matrix_world(obj, placement_matrix))
@@ -1274,12 +1392,21 @@ class IfcImporter:
last_obj = obj
if not last_obj:
return
# temporarily unhide types collection to make sure all objects will be cleaned
project_collection = bpy.context.view_layer.layer_collection.children[self.project["blender"].name]
types_collection = project_collection.children[self.type_collection.name]
types_collection.hide_viewport = False
bpy.context.view_layer.objects.active = last_obj
context_override = {}
bpy.ops.object.editmode_toggle(context_override)
bpy.ops.mesh.tris_convert_to_quads(context_override)
bpy.ops.mesh.normals_make_consistent(context_override)
bpy.ops.object.editmode_toggle(context_override)
types_collection.hide_viewport = True
bpy.context.view_layer.objects.active = last_obj
IfcStore.edited_objs.clear()
def load_file(self):
@@ -1359,27 +1486,8 @@ class IfcImporter:
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()
def create_type_collection(self):
for collection in self.project["blender"].children:
if collection.name == "Types":
self.type_collection = collection
break
if not self.type_collection:
self.type_collection = bpy.data.collections.new("Types")
self.project["blender"].children.link(self.type_collection)
def create_views_collection(self):
view_collection = None
for collection in self.project["blender"].children:
if collection.name == "Views":
view_collection = collection
break
if not view_collection:
view_collection = bpy.data.collections.new("Views")
self.project["blender"].children.link(view_collection)
tool.Loader.create_project_collection("Views")
self.type_collection = tool.Loader.create_project_collection("Types")
def create_spatial_decomposition_collection(self, parent, related_objects):
for element in related_objects:
@@ -1562,7 +1670,10 @@ class IfcImporter:
return rel.RelatingGroup
def get_element_matrix(self, element):
result = ifcopenshell.util.placement.get_local_placement(element.ObjectPlacement)
if isinstance(element, ifcopenshell.sqlite_entity):
result = self.geometry_cache["shapes"][element.id()]["matrix"]
else:
result = ifcopenshell.util.placement.get_local_placement(element.ObjectPlacement)
result[0][3] *= self.unit_scale
result[1][3] *= self.unit_scale
result[2][3] *= self.unit_scale
@@ -1712,6 +1823,10 @@ class IfcImporter:
loop_total = [3] * num_loops
num_vertex_indices = len(geometry.faces)
# See bug 3546
# ios_edges holds true edges that aren't triangulated.
mesh["ios_edges"] = list(set(tuple(e) for e in ifcopenshell.util.shape.get_edges(geometry)))
mesh.vertices.add(num_vertices)
mesh.vertices.foreach_set("co", verts)
mesh.loops.add(num_vertex_indices)
@@ -1719,7 +1834,7 @@ class IfcImporter:
mesh.polygons.add(num_loops)
mesh.polygons.foreach_set("loop_start", loop_start)
mesh.polygons.foreach_set("loop_total", loop_total)
mesh.polygons.foreach_set("use_smooth", [0]*total_faces)
mesh.polygons.foreach_set("use_smooth", [0] * total_faces)
mesh.update()
else:
e = geometry.edges
@@ -1796,8 +1911,9 @@ class IfcImportSettings:
self.should_use_cpu_multiprocessing = True
self.merge_mode = None
self.should_merge_materials_by_colour = False
self.should_load_geometry = True
self.should_use_native_meshes = False
self.should_clean_mesh = True
self.should_clean_mesh = False
self.should_cache = True
self.is_coordinating = True
self.deflection_tolerance = 0.001
@@ -1825,6 +1941,7 @@ class IfcImportSettings:
settings.should_use_cpu_multiprocessing = props.should_use_cpu_multiprocessing
settings.merge_mode = props.merge_mode
settings.should_merge_materials_by_colour = props.should_merge_materials_by_colour
settings.should_load_geometry = props.should_load_geometry
settings.should_use_native_meshes = props.should_use_native_meshes
settings.should_clean_mesh = props.should_clean_mesh
settings.should_cache = props.should_cache
@@ -22,13 +22,13 @@ from blenderbim.bim.ifc import IfcStore
class BIM_PT_aggregate(Panel):
bl_label = "IFC Aggregates"
bl_label = "Aggregates"
bl_idname = "BIM_PT_aggregate"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "object"
bl_parent_id = "BIM_PT_object_metadata"
bl_parent_id = "BIM_PT_tab_object_metadata"
@classmethod
def poll(cls, context):
@@ -54,12 +54,12 @@ def draw_ui(context, layout, obj_type, attributes):
class BIM_PT_object_attributes(Panel):
bl_label = "IFC Attributes"
bl_label = "Attributes"
bl_idname = "BIM_PT_object_attributes"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "object"
bl_parent_id = "BIM_PT_object_metadata"
bl_parent_id = "BIM_PT_tab_object_metadata"
@classmethod
def poll(cls, context):
@@ -76,7 +76,7 @@ class BIM_PT_object_attributes(Panel):
class BIM_PT_material_attributes(Panel):
bl_label = "IFC Material Attributes"
bl_label = "Material Attributes"
bl_idname = "BIM_PT_material_attributes"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
@@ -26,7 +26,7 @@ class BIM_PT_augin(bpy.types.Panel):
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_integrations"
bl_parent_id = "BIM_PT_tab_sandbox"
def draw(self, context):
layout = self.layout
@@ -57,6 +57,7 @@ classes = (
operator.SelectBcfBimSnippetReference,
operator.SelectBcfDocumentReference,
operator.SelectBcfHeaderFile,
operator.UnloadBcfProject,
operator.ViewBcfTopic,
prop.BcfReferenceLink,
prop.BcfLabel,
@@ -58,7 +58,6 @@ class LoadBcfProject(bpy.types.Operator):
filter_glob: bpy.props.StringProperty(default="*.bcf;*.bcfzip", options={"HIDDEN"})
def execute(self, context):
context.scene.BCFProperties.is_loaded = False
if self.filepath:
bcfstore.BcfStore.bcfxml = bcf.bcfxml.load(self.filepath)
bcfxml = bcfstore.BcfStore.get_bcfxml()
@@ -72,6 +71,17 @@ class LoadBcfProject(bpy.types.Operator):
return {"RUNNING_MODAL"}
class UnloadBcfProject(bpy.types.Operator):
bl_idname = "bim.unload_bcf_project"
bl_label = "Unload BCF Project"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
bcfstore.BcfStore.set(None)
context.scene.BCFProperties.is_loaded = False
return {"FINISHED"}
class LoadBcfTopics(bpy.types.Operator):
bl_idname = "bim.load_bcf_topics"
bl_label = "Load BCF Topics"
@@ -29,7 +29,7 @@ class BIM_PT_bcf(Panel):
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_collaboration"
bl_parent_id = "BIM_PT_tab_collaboration"
def draw(self, context):
layout = self.layout
@@ -39,14 +39,16 @@ class BIM_PT_bcf(Panel):
scene = context.scene
props = scene.BCFProperties
row = layout.row(align=True)
row.operator("bim.new_bcf_project", text="New Project")
row.operator("bim.load_bcf_project", text="Load Project")
if not props.is_loaded:
row = layout.row(align=True)
row.operator("bim.new_bcf_project", text="New Project")
row.operator("bim.load_bcf_project", text="Load Project")
return
row.operator("bim.save_bcf_project", text="Save Project")
row = layout.row(align=True)
row.operator("bim.save_bcf_project", icon="EXPORT", text="Save Project")
row.operator("bim.unload_bcf_project", text="", icon="CANCEL")
row = layout.row()
row.prop(props, "name")
@@ -27,7 +27,7 @@ class BIM_PT_qa(Panel):
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_quality_control"
bl_parent_id = "BIM_PT_tab_quality_control"
def draw(self, context):
self.layout.use_property_split = True
@@ -103,10 +103,8 @@ class Loader:
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
matrix = mathutils.Matrix(
ifcopenshell.util.placement.get_axis2placement(surface.BasisSurface.Position).tolist()
)
matrix[0][3] *= unit_scale
matrix[1][3] *= unit_scale
matrix[2][3] *= unit_scale
)
matrix.translation *= unit_scale
mesh.transform(matrix)
return mesh
@@ -691,7 +689,7 @@ class AddBoundary(bpy.types.Operator, tool.Ifc.Operator):
mat.col[0][:3] = x_axis
mat.col[1][:3] = y_axis
mat.col[2][:3] = z_axis
mat.col[3][:3] = p1
mat.translation = p1
return mat
@@ -725,7 +723,7 @@ class AddBoundary(bpy.types.Operator, tool.Ifc.Operator):
def export_surface(self, polygon, target_face_matrix):
x_axis = target_face_matrix.col[0][:3]
z_axis = target_face_matrix.col[2][:3]
p1 = target_face_matrix.col[3][:3]
p1 = target_face_matrix.translation
self.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
tool.Model.unit_scale = self.unit_scale
@@ -25,7 +25,7 @@ from blenderbim.bim.module.boundary.data import SpaceBoundariesData
class BIM_PT_SceneBoundaries(Panel):
bl_label = "IFC Space Boundaries"
bl_label = "Space Boundaries"
bl_id_name = "BIM_PT_scene_boundaries"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
@@ -45,12 +45,12 @@ class BIM_PT_SceneBoundaries(Panel):
class BIM_PT_Boundary(Panel):
bl_label = "IFC Space Boundary"
bl_label = "Space Boundary"
bl_idname = "BIM_PT_Boundary"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "object"
bl_parent_id = "BIM_PT_geometry_object"
bl_parent_id = "BIM_PT_tab_geometric_relationships"
@classmethod
def poll(cls, context):
@@ -117,14 +117,14 @@ class BIM_PT_Boundary(Panel):
class BIM_PT_SpaceBoundaries(Panel):
bl_label = "IFC Space Boundaries"
bl_label = "Space Boundaries"
bl_idname = "BIM_PT_SpaceBoundaries"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "object"
bl_order = 1
bl_parent_id = "BIM_PT_geometry_object"
bl_parent_id = "BIM_PT_tab_geometric_relationships"
@classmethod
def poll(cls, context):
@@ -21,7 +21,7 @@ from . import ui, prop, operator
classes = (
operator.AddBrick,
operator.AddBrickFeed,
operator.AddBrickRelation,
operator.AssignBrickReference,
operator.CloseBrickProject,
operator.ConvertBrickProject,
@@ -33,6 +33,9 @@ classes = (
operator.RewindBrickClass,
operator.ViewBrickClass,
operator.ViewBrickItem,
operator.SerializeBrick,
operator.AddBrickNamespace,
operator.RemoveBrickRelation,
prop.Brick,
prop.BIMBrickProperties,
ui.BIM_PT_brickschema,
@@ -41,9 +41,7 @@ class BrickschemaData:
cls.is_loaded = True
cls.data = {
"is_loaded": cls.get_is_loaded(),
"attributes": cls.attributes(),
"namespaces": cls.namespaces(),
"brick_equipment_classes": cls.brick_equipment_classes(),
"active_relations": cls.active_relations(),
}
@classmethod
@@ -51,7 +49,7 @@ class BrickschemaData:
return BrickStore.graph is not None
@classmethod
def attributes(cls):
def active_relations(cls):
if BrickStore.graph is None:
return []
props = bpy.context.scene.BIMBrickProperties
@@ -59,76 +57,55 @@ class BrickschemaData:
brick = props.bricks[props.active_brick_index]
except:
return []
results = []
uri = brick.uri
namespace = str(uri.split("#")[0])
if namespace == "https://brickschema.org/schema/Brick":
return []
results = []
query = BrickStore.graph.query(
"""
PREFIX brick: <https://brickschema.org/schema/Brick#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
SELECT DISTINCT ?name ?value ?sp ?sv WHERE {
<{uri}> ?name ?value .
SELECT DISTINCT ?predicate ?object ?sp ?sv WHERE {
<{uri}> ?predicate ?object .
OPTIONAL {
{ ?name rdfs:range brick:TimeseriesReference . }
{ ?predicate rdfs:range brick:TimeseriesReference . }
UNION
{ ?name a brick:EntityProperty . }
?value ?sp ?sv }
{ ?predicate a brick:EntityProperty . }
?object ?sp ?sv }
}
""".replace(
"{uri}", uri
)
)
for row in query:
name = row.get("name").toPython().split("#")[-1]
value = row.get("value")
predicate = row.get("predicate")
predicate_name = predicate.toPython().split("#")[-1]
object = row.get("object")
object_name = object.toPython().split("#")[-1]
results.append(
{
"name": name,
"value": value.toPython().split("#")[-1],
"is_uri": isinstance(value, URIRef),
"value_uri": value.toPython(),
"is_globalid": name == "globalID",
"predicate": predicate,
"predicate_name": predicate_name,
"object": object,
"object_name": object_name,
"is_uri": isinstance(object, URIRef),
"object_uri": object.toPython(),
"is_globalid": predicate == "globalID",
}
)
if isinstance(row.get("value"), BNode):
for s, p, o in BrickStore.graph.triples((value, None, None)):
results.append(
{
"name": name + ":" + p.toPython().split("#")[-1],
"value": o.toPython().split("#")[-1],
"is_uri": isinstance(o, URIRef),
"value_uri": o.toPython(),
"is_globalid": p.toPython().split("#")[-1] == "globalID",
}
)
return results
@classmethod
def namespaces(cls):
if BrickStore.graph is None:
return []
results = []
for alias, uri in BrickStore.graph.namespaces():
results.append((uri, f"{alias}: {uri}", ""))
return results
@classmethod
def brick_equipment_classes(cls):
if BrickStore.graph is None:
return []
results = []
query = BrickStore.graph.query(
"""
PREFIX brick: <https://brickschema.org/schema/Brick#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
SELECT ?class WHERE {
?class rdfs:subClassOf* brick:Equipment .
}
"""
)
for uri in sorted([x[0].toPython() for x in query]):
results.append((uri, uri.split("#")[-1], ""))
# if isinstance(row.get("object"), BNode):
# for s, p, o in BrickStore.graph.triples((object, None, None)):
# results.append(
# {
# "predicate": predicate + ":" + p.toPython().split("#")[-1],
# "object": o.toPython().split("#")[-1],
# "is_uri": isinstance(o, URIRef),
# "object_uri": o.toPython(),
# "is_globalid": p.toPython().split("#")[-1] == "globalID",
# }
# )
return results
@@ -16,17 +16,19 @@
# 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 os
import bpy
import ifcopenshell.api
import blenderbim.tool as tool
import blenderbim.core.brick as core
import blenderbim.bim.handler
from blenderbim.bim.ifc import IfcStore
from blenderbim.tool.brick import BrickStore
class Operator:
def execute(self, context):
self._execute(context)
IfcStore.execute_ifc_operator(self, context)
blenderbim.bim.handler.refresh_ui_data()
return {"FINISHED"}
@@ -35,11 +37,16 @@ class LoadBrickProject(bpy.types.Operator, Operator):
bl_idname = "bim.load_brick_project"
bl_label = "Load Brickschema Project"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Load in a Brick project from a file"
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
filter_glob: bpy.props.StringProperty(default="*.ttl", options={"HIDDEN"})
def _execute(self, context):
core.load_brick_project(tool.Brick, filepath=self.filepath)
if os.path.exists(self.filepath) and "ttl" in os.path.splitext(self.filepath)[1].lower():
root = context.scene.BIMBrickProperties.brick_list_root
core.load_brick_project(tool.Brick, filepath=self.filepath, brick_root=root)
else:
self.report({'ERROR'}, f'Failed to load {self.filepath}')
def invoke(self, context, event):
context.window_manager.fileselect_add(self)
@@ -50,35 +57,42 @@ class ViewBrickClass(bpy.types.Operator, Operator):
bl_idname = "bim.view_brick_class"
bl_label = "View Brick Class"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Inspect the subclasses of this class"
brick_class: bpy.props.StringProperty(name="Brick Class")
split_screen: bpy.props.BoolProperty(name="Split Screen", default=False, options={"HIDDEN"})
def _execute(self, context):
core.view_brick_class(tool.Brick, brick_class=self.brick_class)
core.view_brick_class(tool.Brick, brick_class=self.brick_class, split_screen=self.split_screen)
class ViewBrickItem(bpy.types.Operator, Operator):
bl_idname = "bim.view_brick_item"
bl_label = "View Brick Item"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Inspect this entity in the viewer"
item: bpy.props.StringProperty(name="Brick Item")
split_screen: bpy.props.BoolProperty(name="Split Screen", default=False, options={"HIDDEN"})
def _execute(self, context):
core.view_brick_item(tool.Brick, item=self.item)
core.view_brick_item(tool.Brick, item=self.item, split_screen=self.split_screen)
class RewindBrickClass(bpy.types.Operator, Operator):
bl_idname = "bim.rewind_brick_class"
bl_label = "Rewind Brick Class"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Go back to the previous list view"
split_screen: bpy.props.BoolProperty(name="Split Screen", default=False, options={"HIDDEN"})
def _execute(self, context):
core.rewind_brick_class(tool.Brick)
core.rewind_brick_class(tool.Brick, split_screen=self.split_screen)
class CloseBrickProject(bpy.types.Operator, Operator):
bl_idname = "bim.close_brick_project"
bl_label = "Close Brick Project"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Close the Brick project"
def _execute(self, context):
core.close_brick_project(tool.Brick)
@@ -113,6 +127,7 @@ class AddBrick(bpy.types.Operator, Operator):
bl_idname = "bim.add_brick"
bl_label = "Add Brick"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Create the Brick entity"
def _execute(self, context):
props = context.scene.BIMBrickProperties
@@ -124,24 +139,32 @@ class AddBrick(bpy.types.Operator, Operator):
tool.Brick,
element=tool.Ifc.get_entity(context.active_object) if context.selected_objects else None,
namespace=props.namespace,
brick_class=props.brick_equipment_class,
brick_class=props.brick_entity_class,
library=library,
label=props.new_brick_label,
)
class AddBrickFeed(bpy.types.Operator, Operator):
bl_idname = "bim.add_brick_feed"
bl_label = "Add Brick Feed"
class AddBrickRelation(bpy.types.Operator, Operator):
bl_idname = "bim.add_brick_relation"
bl_label = "Add Brick Relation"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Create the Brick relationship"
def _execute(self, context):
source = tool.Ifc.get_entity([o for o in context.selected_objects if o != context.active_object][0])
destination = tool.Ifc.get_entity(context.active_object)
core.add_brick_feed(
tool.Ifc,
props = context.scene.BIMBrickProperties
brick = props.bricks[props.active_brick_index]
if props.new_brick_relation_type == "http://www.w3.org/2000/01/rdf-schema#label":
object = props.new_brick_relation_object
elif props.split_screen_toggled:
object = props.split_screen_bricks[props.split_screen_active_brick_index].uri
else:
object = props.new_brick_relation_namespace + props.new_brick_relation_object
core.add_brick_relation(
tool.Brick,
source=source,
destination=destination,
brick_uri=brick.uri,
predicate=props.new_brick_relation_type,
object=object
)
@@ -158,28 +181,55 @@ class ConvertIfcToBrick(bpy.types.Operator, Operator):
core.convert_ifc_to_brick(tool.Brick, namespace=props.namespace, library=library)
class NewBrickFile(bpy.types.Operator, Operator):
class NewBrickFile(bpy.types.Operator):
bl_idname = "bim.new_brick_file"
bl_label = "New Brick File"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Create a Brick project from scratch"
def execute(self, context):
IfcStore.begin_transaction(self)
IfcStore.add_transaction_operation(self, rollback=self.rollback, commit=lambda data: True)
self._execute(context)
self.transaction_data = {
"schema": BrickStore.schema,
"path": BrickStore.path,
"graph": BrickStore.graph,
}
IfcStore.add_transaction_operation(self, rollback=lambda data: True, commit=self.commit)
IfcStore.end_transaction(self)
blenderbim.bim.handler.refresh_ui_data()
return {"FINISHED"}
def _execute(self, context):
core.new_brick_file(tool.Brick)
root = context.scene.BIMBrickProperties.brick_list_root
core.new_brick_file(tool.Brick, brick_root=root)
def rollback(self, data):
BrickStore.purge()
def commit(self, data):
BrickStore.schema = data["schema"]
BrickStore.path = data["path"]
BrickStore.graph = data["graph"]
class RefreshBrickViewer(bpy.types.Operator, Operator):
bl_idname = "bim.refresh_brick_viewer"
bl_label = "Refresh Brick Viewer"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Refresh the list view"
def _execute(self, context):
core.refresh_brick_viewer(tool.Brick)
core.refresh_brick_viewer(tool.Brick, split_screen=True)
class RemoveBrick(bpy.types.Operator, Operator):
bl_idname = "bim.remove_brick"
bl_label = "Remove Brick"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Delete this entity"
def _execute(self, context):
props = context.scene.BIMBrickProperties
@@ -189,3 +239,57 @@ class RemoveBrick(bpy.types.Operator, Operator):
library=tool.Ifc.get().by_id(int(props.libraries)) if props.libraries else None,
brick_uri=props.bricks[props.active_brick_index].uri,
)
class SerializeBrick(bpy.types.Operator):
bl_idname = "bim.serialize_brick"
bl_label = "Serialize Brick"
filter_glob: bpy.props.StringProperty(default="*.ttl", options={"HIDDEN"})
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
should_save_as: bpy.props.BoolProperty(name="Should Save As", default=False, options={"HIDDEN"})
def invoke(self, context, event):
if self.should_save_as or not BrickStore.path:
WindowManager = context.window_manager
WindowManager.fileselect_add(self)
return {"RUNNING_MODAL"}
else:
return self.execute(context)
def execute(self, context):
if self.should_save_as or not BrickStore.path:
BrickStore.path = self.filepath
core.serialize_brick(tool.Brick)
return {"FINISHED"}
@classmethod
def description(cls, context, properties):
if properties.should_save_as:
return "Save Brick project to a selected file"
return "Save the Brick project"
class AddBrickNamespace(bpy.types.Operator, Operator):
bl_idname = "bim.add_brick_namespace"
bl_label = "Add Brick Namespace"
bl_description = "Bind a new namespace to the Brick project"
def _execute(self, context):
props = context.scene.BIMBrickProperties
alias = props.new_brick_namespace_alias
uri = props.new_brick_namespace_uri
core.add_namespace(tool.Brick, alias=alias, uri=uri)
class RemoveBrickRelation(bpy.types.Operator, Operator):
bl_idname = "bim.remove_brick_relation"
bl_label = "Remove Relation"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Delete this relationship"
predicate: bpy.props.StringProperty(name="Relation")
object: bpy.props.StringProperty(name="Object")
def _execute(self, context):
props = context.scene.BIMBrickProperties
brick = props.bricks[props.active_brick_index]
core.remove_brick_relation(tool.Brick, brick_uri=brick.uri, predicate=self.predicate, object=self.object)
@@ -30,7 +30,9 @@ from bpy.props import (
FloatVectorProperty,
CollectionProperty,
)
import blenderbim.core.brick as core
import blenderbim.tool.brick as tool
from blenderbim.tool.brick import BrickStore
def update_active_brick_index(self, context):
BrickschemaData.is_loaded = False
@@ -43,15 +45,35 @@ def get_libraries(self, context):
def get_namespaces(self, context):
if not BrickschemaData.is_loaded:
BrickschemaData.load()
return BrickschemaData.data["namespaces"]
return BrickStore.namespaces
def get_brick_equipment_classes(self, context):
if not BrickschemaData.is_loaded:
BrickschemaData.load()
return BrickschemaData.data["brick_equipment_classes"]
def get_brick_entity_classes(self, context):
entity = self.brick_entity_create_type
return BrickStore.entity_classes[entity]
def get_brick_roots(self, context):
return [(root, root, "") for root in BrickStore.root_classes]
def get_brick_relations(self, context):
def is_label(relation):
return relation["predicate_name"] == "label"
if not list(filter(is_label, BrickschemaData.data["active_relations"])):
new_relations = BrickStore.relationships.copy()
new_relations.append(("http://www.w3.org/2000/01/rdf-schema#label", "label", ""))
return new_relations
return BrickStore.relationships
def update_view(self, context):
root = context.scene.BIMBrickProperties.brick_list_root
core.set_brick_list_root(tool.Brick, brick_root=root, split_screen=False)
def split_screen_update_view(self, context):
root = context.scene.BIMBrickProperties.split_screen_brick_list_root
core.set_brick_list_root(tool.Brick, brick_root=root, split_screen=True)
class Brick(PropertyGroup):
@@ -67,5 +89,28 @@ class BIMBrickProperties(PropertyGroup):
bricks: CollectionProperty(name="Bricks", type=Brick)
active_brick_index: IntProperty(name="Active Brick Index", update=update_active_brick_index)
libraries: EnumProperty(name="Libraries", items=get_libraries)
set_list_root_toggled: BoolProperty(name="Set List Root Toggled", default=False)
brick_list_root: EnumProperty(name="Brick List Root", items=get_brick_roots, update=update_view)
# namespace manager
namespace: EnumProperty(name="Namespace", items=get_namespaces)
brick_equipment_class: EnumProperty(name="Brick Equipment Class", items=get_brick_equipment_classes)
brick_settings_toggled: BoolProperty(name="Brick Settings Toggled", default=False)
new_brick_namespace_alias: StringProperty(name="New Brick Namespace Alias")
new_brick_namespace_uri: StringProperty(name="New Brick Namespace URI")
# create brick entity
new_brick_label: StringProperty(name="New Brick Label")
brick_entity_create_type: EnumProperty(name="Brick Entity Types", items=get_brick_roots)
brick_entity_class: EnumProperty(name="Brick Equipment Class", items=get_brick_entity_classes)
# create relations
brick_create_relations_toggled: BoolProperty(name="Brick Create Relations Toggled", default=False)
brick_edit_relations_toggled: BoolProperty(name="Brick Edit Relations Toggled", default=False)
new_brick_relation_type: EnumProperty(name="New Brick Relation Type", items=get_brick_relations)
new_brick_relation_namespace: EnumProperty(name="New Brick Relation Namespace", items=get_namespaces)
new_brick_relation_object: StringProperty(name="New Brick Relation Object")
add_relation_failed: BoolProperty(name="Add Relation Failed", default=False)
# create relations split screen
split_screen_toggled: BoolProperty(name="Split Screen Toggled", default=False)
split_screen_bricks: CollectionProperty(name="Split Screen Bricks", type=Brick)
split_screen_active_brick_index: IntProperty(name="Split Screen Active Brick Index", update=update_active_brick_index)
split_screen_active_brick_class: StringProperty(name="Split Screen Active Brick Class")
split_screen_brick_breadcrumbs: CollectionProperty(name="Split Screen Brick Breadcrumbs", type=StrProperty)
split_screen_brick_list_root: EnumProperty(name="Split Screen Brick List Root", items=get_brick_roots, update=split_screen_update_view)
+143 -22
View File
@@ -20,15 +20,16 @@ import blenderbim.tool as tool
from bpy.types import Panel, UIList
from blenderbim.bim.helper import prop_with_search
from blenderbim.bim.module.brick.data import BrickschemaData, BrickschemaReferencesData
from blenderbim.tool.brick import BrickStore
class BIM_PT_brickschema(Panel):
bl_label = "Brickschema Project"
bl_idname = "BIM_PT_brickschema"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_tab_operations"
def draw(self, context):
if not BrickschemaData.is_loaded:
@@ -42,38 +43,155 @@ class BIM_PT_brickschema(Panel):
return
row = self.layout.row(align=True)
if len(self.props.brick_breadcrumbs):
row.operator("bim.rewind_brick_class", text="", icon="FRAME_PREV")
row.label(text=self.props.active_brick_class)
row.operator("bim.refresh_brick_viewer", text="", icon="FILE_REFRESH")
if BrickStore.path:
row.label(text=BrickStore.path, icon="FILEBROWSER")
else:
row.label(text="No file", icon="FILEBROWSER")
row = self.layout.row(align=True)
if BrickStore.last_saved:
row.label(text=BrickStore.last_saved, icon="TIME")
else:
row.label(text="Not saved", icon="TIME")
row = self.layout.row(align=True)
op = row.operator("bim.serialize_brick", icon="EXPORT", text="Save")
op.should_save_as = False
op = row.operator("bim.serialize_brick", icon="FILE_TICK", text="Save As")
op.should_save_as = True
row.operator("bim.close_brick_project", text="", icon="CANCEL")
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(data=self.props, property="brick_settings_toggled", text="", icon="PREFERENCES")
if self.props.brick_settings_toggled:
box = self.layout.box()
row = box.row(align=True)
row.label(text="Active Namespace:")
row = box.row(align=True)
prop_with_search(row, self.props, "namespace", text="")
row = box.row(align=True)
row.label(text="Bind New Namespace:")
row = box.row(align=True)
row.prop(data=self.props, property="new_brick_namespace_alias", text="")
col = row.column()
col.alignment = "CENTER"
col.scale_x = 1.1
col.label(text=":")
row.prop(data=self.props, property="new_brick_namespace_uri", text="")
row.operator("bim.add_brick_namespace", text="", icon="ADD")
row = self.layout.row(align=True)
row.label(text="Create Entity:")
row = self.layout.row(align=True)
row.prop(data=self.props, property="brick_entity_create_type", text="")
row = self.layout.row(align=True)
row.prop(data=self.props, property="new_brick_label", text="")
prop_with_search(row, self.props, "brick_entity_class", text="")
row.operator("bim.add_brick", text="", icon="ADD")
row = self.layout.row(align=True)
row.alignment = "RIGHT"
row.operator("bim.add_brick_feed", text="", icon="PLUGIN")
row.operator("bim.remove_brick", text="", icon="X")
col = row.column()
col.alignment = "RIGHT"
row.prop(data=self.props, property="set_list_root_toggled", text="", icon="OUTLINER")
row.prop(data=self.props, property="split_screen_toggled", text="", icon="WINDOW")
row.operator("bim.refresh_brick_viewer", text="", icon="FILE_REFRESH")
self.layout.template_list("BIM_UL_bricks", "", self.props, "bricks", self.props, "active_brick_index")
grid = self.layout.grid_flow(even_columns=True)
grid_left = grid.column(align=True)
row = grid_left.row(align=True)
if len(self.props.brick_breadcrumbs):
op = row.operator("bim.rewind_brick_class", text="", icon="FRAME_PREV")
op.split_screen = False
row.label(text=self.props.active_brick_class)
for attribute in BrickschemaData.data["attributes"]:
if self.props.set_list_root_toggled:
row = grid_left.row(align=True)
row.prop(data=self.props, property="brick_list_root", text="")
row = grid_left.row()
BIM_UL_bricks.split_screen = False
row.template_list("BIM_UL_bricks", "", self.props, "bricks", self.props, "active_brick_index")
if self.props.split_screen_toggled:
grid_right = grid.column(align=True)
row = grid_right.row(align=True)
if len(self.props.split_screen_brick_breadcrumbs):
op = row.operator("bim.rewind_brick_class", text="", icon="FRAME_PREV")
op.split_screen = True
row.label(text=self.props.split_screen_active_brick_class)
if self.props.set_list_root_toggled:
row = grid_right.row(align=True)
row.prop(data=self.props, property="split_screen_brick_list_root", text="")
row = grid_right.row()
BIM_UL_bricks.split_screen = True
row.template_list("BIM_UL_bricks", "", self.props, "split_screen_bricks", self.props, "split_screen_active_brick_index")
if BrickschemaData.data["active_relations"]:
row = self.layout.row(align=True)
row.label(text=attribute["name"])
row.label(text=attribute["value"])
if attribute["is_uri"]:
col = row.column()
col.alignment = "RIGHT"
row.prop(data=self.props, property="brick_create_relations_toggled", text="", icon="PLUGIN")
row.prop(data=self.props, property="brick_edit_relations_toggled", text="", icon="TOOL_SETTINGS")
row.operator("bim.remove_brick", text="", icon="X")
row = self.layout.row(align=True)
row.label(text="Create Relation:")
if self.props.brick_create_relations_toggled and self.props.new_brick_relation_type == "http://www.w3.org/2000/01/rdf-schema#label":
row = self.layout.row(align=True)
prop_with_search(row, self.props, "new_brick_relation_type", text="")
row.prop(data=self.props, property="new_brick_relation_object", text="")
row.operator("bim.add_brick_relation", text="", icon="ADD")
elif self.props.brick_create_relations_toggled and self.props.split_screen_toggled:
row = self.layout.row(align=True)
split_screen_selection = self.props.split_screen_bricks[self.props.split_screen_active_brick_index]
if split_screen_selection.total_items:
row.label(text="No selection", icon="INFO")
else:
prop_with_search(row, self.props, "new_brick_relation_type", text="")
row.label(text=split_screen_selection.label if split_screen_selection.label else split_screen_selection.name)
row.operator("bim.add_brick_relation", text="", icon="ADD")
elif self.props.brick_create_relations_toggled:
row = self.layout.row(align=True)
prop_with_search(row, self.props, "new_brick_relation_namespace", text="")
row = self.layout.row(align=True)
prop_with_search(row, self.props, "new_brick_relation_type", text="")
row.prop(data=self.props, property="new_brick_relation_object", text="")
row.operator("bim.add_brick_relation", text="", icon="ADD")
if self.props.brick_create_relations_toggled and self.props.add_relation_failed:
row = self.layout.row(align=True)
row.label(text="Failed to find this entity!", icon="ERROR")
for relation in BrickschemaData.data["active_relations"]:
row = self.layout.row(align=True)
row.label(text=relation["predicate_name"])
row.label(text=relation["object_name"])
if self.props.brick_edit_relations_toggled and relation["predicate_name"] != "type":
op = row.operator("bim.remove_brick_relation", text="", icon="UNLINKED")
op.predicate = relation["predicate"]
op.object = relation["object"]
if relation["is_uri"] and relation["predicate_name"] != "type":
op = row.operator("bim.view_brick_item", text="", icon="DISCLOSURE_TRI_RIGHT")
op.item = attribute["value_uri"]
if attribute["is_globalid"]:
op.item = relation["object_uri"]
if relation["is_globalid"]:
op = row.operator("bim.select_global_id", icon="RESTRICT_SELECT_OFF", text="")
op.global_id = attribute["value"]
op.global_id = relation["object_name"]
class BIM_PT_ifc_brickschema_references(Panel):
bl_label = "IFC Brickschema References"
bl_label = "Brickschema References"
bl_idname = "BIM_PT_ifc_brickschema_references"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
@@ -122,12 +240,15 @@ class BIM_PT_ifc_brickschema_references(Panel):
class BIM_UL_bricks(UIList):
split_screen = False
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
if item:
row = layout.row(align=True)
label = item.label if item.label else item.name
if item.total_items:
op = row.operator("bim.view_brick_class", text="", icon="DISCLOSURE_TRI_RIGHT", emboss=False)
op.brick_class = item.name
row.label(text=item.label if item.label else item.name)
if item.total_items:
row.label(text=str(item.total_items))
op.split_screen = self.split_screen
label = label + " (" + str(item.total_items) + ")"
row.label(text=label)
@@ -137,24 +137,22 @@ class CadTool(WorkSpaceTool):
else:
if (
(RailingData.is_loaded or not RailingData.load())
and RailingData.data["parameters"]
and RailingData.data["pset_data"]
and context.active_object.BIMRailingProperties.is_editing_path
):
row = layout.row(align=True)
row.label(text="", icon="EVENT_SHIFT")
row.label(text="", icon="EVENT_Q")
row.operator("bim.cad_hotkey", text="Apply Railing Path").hotkey = "S_Q"
row.label(text="", icon=f"EVENT_TAB")
row.operator("bim.finish_editing_railing_path")
row.operator("bim.cancel_editing_railing_path", icon="CANCEL", text="")
elif (
(RoofData.is_loaded or not RoofData.load())
and RoofData.data["parameters"]
and RoofData.data["pset_data"]
and context.active_object.BIMRoofProperties.is_editing_path
):
row = layout.row(align=True)
row.label(text="", icon="EVENT_SHIFT")
row.label(text="", icon="EVENT_Q")
row.operator("bim.cad_hotkey", text="Apply Roof Path").hotkey = "S_Q"
row.label(text="", icon=f"EVENT_TAB")
row.operator("bim.finish_editing_roof_path")
row.operator("bim.cancel_editing_roof_path", icon="CANCEL", text="")
row = layout.row(align=True)
@@ -229,7 +227,7 @@ class CadHotkey(bpy.types.Operator):
row.prop(props, "y")
elif (
(RoofData.is_loaded or not RoofData.load())
and RoofData.data["parameters"]
and RoofData.data["pset_data"]
and bpy.context.active_object.BIMRoofProperties.is_editing_path
):
self.layout.row().prop(props, "gable_roof_edge_angle")
@@ -270,26 +268,12 @@ class CadHotkey(bpy.types.Operator):
elif bpy.context.active_object.data.BIMMeshProperties.subshape_type == "AXIS":
bpy.ops.bim.edit_extrusion_axis()
elif (
(RailingData.is_loaded or not RailingData.load())
and RailingData.data["parameters"]
and bpy.context.active_object.BIMRailingProperties.is_editing_path
):
bpy.ops.bim.finish_editing_railing_path()
elif (
(RoofData.is_loaded or not RoofData.load())
and RoofData.data["parameters"]
and bpy.context.active_object.BIMRoofProperties.is_editing_path
):
bpy.ops.bim.finish_editing_roof_path()
def hotkey_S_R(self):
if self.is_profile():
bpy.ops.bim.add_rectangle(x=self.props.x, y=self.props.y)
elif (
(RoofData.is_loaded or not RoofData.load())
and RoofData.data["parameters"]
and RoofData.data["pset_data"]
and bpy.context.active_object.BIMRoofProperties.is_editing_path
):
bpy.ops.bim.set_gable_roof_edge_angle(
@@ -23,7 +23,7 @@ import bmesh
import logging
import numpy as np
import ifcopenshell
from mathutils import Matrix
from mathutils import Matrix, Vector
from math import radians
from blenderbim.bim.ifc import IfcStore
@@ -222,6 +222,7 @@ class ExecuteIfcClash(bpy.types.Operator):
_, extension = os.path.splitext(self.filepath)
if extension != ".json":
self.filepath = bpy.path.ensure_ext(self.filepath, ".bcf")
settings = ifcclash.ClashSettings()
settings.output = self.filepath
settings.logger = logging.getLogger("Clash")
@@ -230,12 +231,28 @@ class ExecuteIfcClash(bpy.types.Operator):
if context.scene.BIMClashProperties.should_create_clash_snapshots:
def get_viewpoint_snapshot(viewpoint, mat):
def get_viewpoint_snapshot(viewpoint):
camera = bpy.data.objects.get("IFC Clash Camera")
if not camera:
camera = bpy.data.objects.new("IFC Clash Camera", bpy.data.cameras.new("IFC Clash Camera"))
context.scene.collection.objects.link(camera)
camera.matrix_world = Matrix(mat)
bcf_camera = viewpoint.visualization_info.perspective_camera
p = bcf_camera.camera_view_point
z = bcf_camera.camera_direction
z = Vector([z.x, z.y, z.z]) * -1
y = bcf_camera.camera_up_vector
y = Vector([y.x, y.y, y.z])
x = y.cross(z)
mat = Matrix([
[x[0], y[0], z[0], p.x],
[x[1], y[1], z[1], p.y],
[x[2], y[2], z[2], p.z],
[0, 0, 0, 0],
])
camera.matrix_world = mat
context.scene.camera = camera
camera.data.angle = radians(60)
area = next(area for area in context.screen.areas if area.type == "VIEW_3D")
@@ -246,7 +263,8 @@ class ExecuteIfcClash(bpy.types.Operator):
context.scene.render.image_settings.file_format = "PNG"
context.scene.render.filepath = os.path.join(context.scene.BIMProperties.data_dir, "snapshot.png")
bpy.ops.render.opengl(write_still=True)
return context.scene.render.filepath
with open(context.scene.render.filepath, "rb") as f:
return ("snapshot.png", f.read())
clasher.get_viewpoint_snapshot = get_viewpoint_snapshot
@@ -21,13 +21,13 @@ from bpy.types import Panel
class BIM_PT_ifcclash(Panel):
bl_label = "IFC Clash Sets"
bl_label = "Clash Sets"
bl_idname = "BIM_PT_ifcclash"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_quality_control"
bl_parent_id = "BIM_PT_tab_quality_control"
def draw(self, context):
layout = self.layout
@@ -30,7 +30,7 @@ from blenderbim.bim.module.classification.data import (
class BIM_PT_classifications(Panel):
bl_label = "IFC Classifications"
bl_label = "Classifications"
bl_idname = "BIM_PT_classifications"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
@@ -156,13 +156,13 @@ class ReferenceUI:
class BIM_PT_classification_references(Panel, ReferenceUI):
bl_label = "IFC Classification References"
bl_label = "Classification References"
bl_idname = "BIM_PT_classification_references"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "object"
bl_parent_id = "BIM_PT_object_metadata"
bl_parent_id = "BIM_PT_tab_object_metadata"
@classmethod
def poll(cls, context):
@@ -180,7 +180,7 @@ class BIM_PT_classification_references(Panel, ReferenceUI):
class BIM_PT_material_classifications(Panel, ReferenceUI):
bl_label = "IFC Material Classifications"
bl_label = "Material Classifications"
bl_idname = "BIM_PT_material_classifications"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
@@ -206,7 +206,7 @@ class BIM_PT_material_classifications(Panel, ReferenceUI):
class BIM_PT_cost_classifications(Panel, ReferenceUI):
bl_label = "IFC Cost Classifications"
bl_label = "Cost Classifications"
bl_idname = "BIM_PT_cost_classifications"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
@@ -16,18 +16,18 @@
# 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.tool as tool
from bpy.types import Panel
from blenderbim.bim.ifc import IfcStore
class BIM_PT_cobie(Panel):
bl_label = "IFC COBie"
bl_label = "COBie"
bl_idname = "BIM_PT_cobie"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_collaboration"
bl_parent_id = "BIM_PT_tab_handover"
def draw(self, context):
layout = self.layout
@@ -23,7 +23,7 @@ from blenderbim.bim.module.constraint.data import ConstraintsData, ObjectConstra
class BIM_PT_constraints(Panel):
bl_label = "IFC Constraints"
bl_label = "Constraints"
bl_idname = "BIM_PT_constraints"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
@@ -68,14 +68,14 @@ class BIM_PT_constraints(Panel):
class BIM_PT_object_constraints(Panel):
bl_label = "IFC Constraints"
bl_label = "Constraints"
bl_idname = "BIM_PT_object_constraints"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "object"
bl_order = 1
bl_parent_id = "BIM_PT_misc_object"
bl_parent_id = "BIM_PT_tab_misc"
@classmethod
def poll(cls, context):
@@ -23,7 +23,7 @@ from blenderbim.bim.module.context.data import ContextData
class BIM_PT_context(bpy.types.Panel):
bl_label = "IFC Geometric Representation Contexts"
bl_label = "Geometric Representation Contexts"
bl_idname = "BIM_PT_context"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
@@ -20,64 +20,65 @@ import bpy
from . import ui, prop, operator
classes = (
operator.AddCostColumn,
operator.AddCostItem,
operator.AddCostItemQuantity,
operator.AddCostSchedule,
operator.RemoveCostSchedule,
operator.EditCostSchedule,
operator.AddCostValue,
operator.AddCurrency,
operator.AddSummaryCostItem,
operator.AssignCostItemQuantity,
operator.AssignCostItemType,
operator.AssignCostValue,
operator.CalculateCostItemResourceValue,
operator.ChangeParentCostItem,
operator.ClearCostItemAssignments,
operator.ContractCostItem,
operator.ContractCostItemRate,
operator.ContractCostItems,
operator.CopyCostItem,
operator.CopyCostItemValues,
operator.DisableEditingCostItem,
operator.DisableEditingCostItemQuantity,
operator.DisableEditingCostItemValue,
operator.DisableEditingCostSchedule,
operator.EditCostItem,
operator.EditCostItemQuantity,
operator.EditCostItemValue,
operator.EditCostItemValueFormula,
operator.EnableEditingCostSchedule,
operator.EnableEditingCostItems,
operator.EditCostSchedule,
operator.EnableEditingCostItem,
operator.ExportCostSchedules,
operator.ExpandCostItems,
operator.EnableEditingCostItemQuantities,
operator.EnableEditingCostItemQuantity,
operator.EnableEditingCostItemValues,
operator.EnableEditingCostItems,
operator.EnableEditingCostItemValue,
operator.EnableEditingCostItemValueFormula,
operator.DisableEditingCostItem,
operator.DisableEditingCostSchedule,
operator.DisableEditingCostItemQuantity,
operator.DisableEditingCostItemValue,
operator.AddCostColumn,
operator.RemoveCostColumn,
operator.AddCostItem,
operator.AddSummaryCostItem,
operator.EnableEditingCostItemValues,
operator.EnableEditingCostSchedule,
operator.ExpandCostItem,
operator.ContractCostItem,
operator.ExpandCostItemRate,
operator.ExpandCostItems,
operator.ExportCostSchedules,
operator.HighlightProductCostItem,
operator.ImportCostScheduleCsv,
operator.LoadCostItemElementQuantities,
operator.LoadCostItemQuantities,
operator.LoadCostItemResourceQuantities,
operator.LoadCostItemTaskQuantities,
operator.LoadCostItemTypes,
operator.LoadProductCostItems,
operator.LoadScheduleOfRates,
operator.RemoveCostColumn,
operator.RemoveCostItem,
operator.AssignCostItemType,
operator.UnassignCostItemType,
operator.AssignCostItemQuantity,
operator.UnassignCostItemQuantity,
operator.AddCostItemQuantity,
operator.RemoveCostItemQuantity,
operator.AddCostValue,
operator.RemoveCostItemValue,
operator.CopyCostItemValues,
operator.RemoveCostSchedule,
operator.ReorderCostItem,
operator.SelectCostItemProducts,
operator.SelectCostScheduleProducts,
operator.ImportCostScheduleCsv,
operator.LoadCostItemQuantities,
operator.LoadCostItemTypes,
operator.AssignCostValue,
operator.LoadScheduleOfRates,
operator.ExpandCostItemRate,
operator.ContractCostItemRate,
operator.CalculateCostItemResourceValue,
operator.ClearCostItemAssignments,
operator.HighlightProductCostItem,
operator.LoadProductCostItems,
operator.ReorderCostItem,
operator.SelectUnassignedProducts,
operator.LoadCostItemElementQuantities,
operator.LoadCostItemTaskQuantities,
operator.LoadCostItemResourceQuantities,
operator.ChangeParentCostItem,
operator.CopyCostItem,
operator.AddCurrency,
operator.UnassignCostItemQuantity,
operator.UnassignCostItemType,
prop.CostItem,
prop.CostItemQuantity,
prop.CostItemType,
@@ -117,6 +117,7 @@ class CostSchedulesData:
data["TotalAppliedValue"] = 0.0
data["TotalCost"] = 0.0
has_unit_basis = False
is_sum = False
if root_element.is_a("IfcCostItem"):
values = root_element.CostValues
elif root_element.is_a("IfcConstructionResource"):
@@ -130,6 +131,11 @@ class CostSchedulesData:
data["UnitBasisValueComponent"] = cost_value_data["UnitBasis"]["ValueComponent"]
data["UnitBasisUnitSymbol"] = cost_value_data["UnitBasis"]["UnitSymbol"]
has_unit_basis = True
else:
data["UnitBasisValueComponent"] = 1
data["UnitBasisUnitSymbol"] = "U"
if cost_value.Category == "*":
is_sum = True
if has_unit_basis:
data["TotalCost"] = data["TotalAppliedValue"] / data["UnitBasisValueComponent"]
else:
@@ -137,7 +143,8 @@ class CostSchedulesData:
data["TotalCost"] = data["TotalAppliedValue"] * data["TotalCostQuantity"]
else:
data["TotalCost"] = data["TotalAppliedValue"]
data["TotalAppliedValue"] = None
if is_sum:
data["TotalAppliedValue"] = None
@classmethod
def _load_cost_item_quantities(cls, cost_item, data):
@@ -154,7 +161,7 @@ class CostSchedulesData:
if unit:
data["UnitSymbol"] = ifcopenshell.util.unit.get_unit_symbol(unit)
else:
data["UnitSymbol"] = None
data["UnitSymbol"] = "U"
# same_unit_nested_cost_item = set()
# data["DerivedTotalCostQuantity"] = None
@@ -156,6 +156,17 @@ class ContractCostItem(bpy.types.Operator, tool.Ifc.Operator):
core.contract_cost_item(tool.Cost, cost_item=tool.Ifc.get().by_id(self.cost_item))
class ContractCostItems(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.contract_cost_items"
bl_label = "Contract Cost Item"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Collapse cost item tree"
cost_item: bpy.props.IntProperty()
def _execute(self, context):
core.contract_cost_items(tool.Cost)
class RemoveCostItem(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.remove_cost_item"
bl_label = "Remove Cost Item"
@@ -163,7 +174,7 @@ class RemoveCostItem(bpy.types.Operator, tool.Ifc.Operator):
cost_item: bpy.props.IntProperty()
def _execute(self, context):
core.remove_cost_item(tool.Ifc, tool.Cost, cost_item=tool.Ifc.get().by_id(self.cost_item))
core.remove_cost_item(tool.Ifc, tool.Cost, cost_item_id=self.cost_item)
class EnableEditingCostItem(bpy.types.Operator, tool.Ifc.Operator):
+26 -31
View File
@@ -24,13 +24,12 @@ from blenderbim.bim.module.cost.data import CostSchedulesData
class BIM_PT_cost_schedules(Panel):
bl_label = "IFC Cost Schedules"
bl_label = "Cost Schedules"
bl_idname = "BIM_PT_cost_schedules"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_4D5D"
bl_parent_id = "BIM_PT_tab_4D5D"
@classmethod
def poll(cls, context):
@@ -48,8 +47,9 @@ class BIM_PT_cost_schedules(Panel):
row.label(text=f"{CostSchedulesData.data['total_cost_schedules']} Cost Schedules Found", icon="TEXT")
row.operator("bim.export_cost_schedules", text="Export as spreadsheet", icon="EXPORT")
else:
row.label(text="No Cost Schedules Found found.", icon="COMMUNITY")
row = self.layout.row()
row.label(text="No Cost Schedules found.", icon="TEXT")
row = self.layout.row(align=True)
row.alignment = "RIGHT"
row.prop(self.props, "cost_schedule_predefined_types")
row.operator("bim.add_cost_schedule", icon="ADD", text="Add")
@@ -139,8 +139,8 @@ class BIM_PT_cost_schedules(Panel):
row = self.layout.row(align=True)
row.alignment = "RIGHT"
row.operator("bim.add_summary_cost_item", text="Add Summary Cost", icon="ADD")
row.operator("bim.expand_all_tasks", text="Expand All")
row.operator("bim.contract_all_tasks", text="Contract All")
row.operator("bim.expand_cost_items", text="Expand All")
row.operator("bim.contract_cost_items", text="Contract All")
row = self.layout.row(align=True)
row.alignment = "RIGHT"
if self.props.cost_items and self.props.active_cost_item_index < len(self.props.cost_items):
@@ -267,7 +267,7 @@ class BIM_PT_cost_schedules(Panel):
class BIM_PT_cost_item_types(Panel):
bl_label = "IFC Cost Item Types"
bl_label = "Cost Item Types"
bl_idname = "BIM_PT_cost_item_types"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
@@ -353,7 +353,7 @@ class BIM_PT_cost_item_types(Panel):
class BIM_PT_cost_item_quantities(Panel):
bl_label = "IFC Cost Item Quantities"
bl_label = "Cost Item Quantities"
bl_idname = "BIM_PT_cost_item_quantities"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
@@ -525,7 +525,7 @@ class BIM_PT_cost_item_quantities(Panel):
class BIM_PT_cost_item_rates(Panel):
bl_label = "IFC Cost Item Rates"
bl_label = "Cost Item Rates"
bl_idname = "BIM_PT_cost_item_rates"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
@@ -620,18 +620,22 @@ class BIM_UL_cost_items_trait:
else:
row.label(text="", icon="DOT")
def draw_total_cost_column(self, layout, cost_item):
format_numbers = "{0:,.2f}".format(cost_item["TotalCost"]).replace(",", " ")
currency = CostSchedulesData.data["currency"]
text = "{} {}".format(format_numbers, currency["name"]) if currency else format_numbers
layout.label(text=text)
def draw_quantity_column(self, layout, cost_item):
if CostSchedulesData.data["is_editing_rates"]:
self.draw_uom_column(layout, cost_item)
else:
self.draw_total_quantity_column(layout, cost_item)
def draw_uom_column(self, layout, cost_item):
layout.label(text=cost_item["UnitBasisUnitSymbol"])
def draw_total_quantity_column(self, layout, cost_item):
if cost_item["TotalCostQuantity"]:
label = "{0:.2f}".format(cost_item["TotalCostQuantity"]) + f" {cost_item['UnitSymbol'] or '-'}"
layout.label(text=label)
else:
layout.label(text="-")
def draw_value_column(self, layout, cost_item):
if cost_item["TotalAppliedValue"]:
text = "{0:,.2f}".format(cost_item["TotalAppliedValue"]).replace(",", " ")
@@ -643,8 +647,11 @@ class BIM_UL_cost_items_trait:
else:
layout.label(text="-")
def draw_uom_column(self, layout, cost_item):
layout.label(text=cost_item["UnitBasisUnitSymbol"] or "-" if cost_item["UnitBasisValueComponent"] else "-")
def draw_total_cost_column(self, layout, cost_item):
format_numbers = "{0:,.2f}".format(cost_item["TotalCost"]).replace(",", " ")
currency = CostSchedulesData.data["currency"]
text = "{} {}".format(format_numbers, currency["name"]) if currency else format_numbers
layout.label(text=text)
def draw_order_operator(self, row, ifc_definition_id, cost_item):
if cost_item["NestingIndex"] is not None:
@@ -657,19 +664,7 @@ class BIM_UL_cost_items_trait:
op.cost_item = ifc_definition_id
op.new_index = cost_item["NestingIndex"] - 1
def draw_total_quantity_column(self, layout, cost_item):
if cost_item["TotalCostQuantity"]:
label = "{0:.2f}".format(cost_item["TotalCostQuantity"]) + f" {cost_item['UnitSymbol'] or '-'}"
layout.label(text=label)
else:
layout.label(text="-")
# if cost_item["DerivedTotalCostQuantity"] not in [None, 0]:
# layout.label(text="{0:.2f}".format(cost_item["DerivedTotalCostQuantity"]) + f" {cost_item['DerivedUnitSymbol'] or '-'}")
# else:
# if cost_item["TotalCostQuantity"] == 0:
# layout.label(text="-")
# else:
# layout.label(text="{0:.2f}".format(cost_item["TotalCostQuantity"]) + f" {cost_item['UnitSymbol'] or '-'}")
class BIM_UL_cost_items(BIM_UL_cost_items_trait, UIList):
@@ -26,7 +26,7 @@ class BIM_PT_covetool(bpy.types.Panel):
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_integrations"
bl_parent_id = "BIM_PT_tab_sandbox"
def draw(self, context):
layout = self.layout
@@ -128,12 +128,13 @@ class ExportCsvAttributes(bpy.types.Operator):
class ExportIfcCsv(bpy.types.Operator):
bl_idname = "bim.export_ifccsv"
bl_label = "Export IFC to CSV"
bl_label = "Export IFC"
filename_ext = ".csv"
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
def invoke(self, context, event):
self.filepath = bpy.path.ensure_ext(bpy.data.filepath, ".csv")
props = context.scene.CsvProperties
self.filepath = bpy.path.ensure_ext(bpy.data.filepath, f".{props.format}")
WindowManager = context.window_manager
WindowManager.fileselect_add(self)
return {"RUNNING_MODAL"}
@@ -142,17 +143,16 @@ class ExportIfcCsv(bpy.types.Operator):
import ifccsv
props = context.scene.CsvProperties
self.filepath = bpy.path.ensure_ext(self.filepath, ".csv")
self.filepath = bpy.path.ensure_ext(self.filepath, f".{props.format}")
if props.should_load_from_memory:
ifc_file = IfcStore.get_file()
else:
ifc_file = ifcopenshell.open(props.csv_ifc_file)
selector = ifcopenshell.util.selector.Selector()
results = selector.parse(ifc_file, props.ifc_selector)
results = ifcopenshell.util.selector.filter_elements(ifc_file, props.ifc_selector)
ifc_csv = ifccsv.IfcCsv()
attributes = [a.name for a in props.csv_attributes]
sep = props.csv_custom_delimiter if props.csv_delimiter == "CUSTOM" else props.csv_delimiter
ifc_csv.export(ifc_file, results, attributes, output=self.filepath, format=args.format, delimiter=sep)
ifc_csv.export(ifc_file, results, attributes, output=self.filepath, format=props.format, delimiter=sep)
return {"FINISHED"}
@@ -194,9 +194,12 @@ class EyedropIfcCsv(bpy.types.Operator):
global_ids = []
self.file = IfcStore.get_file()
for obj in context.selected_objects:
if hasattr(obj, "BIMObjectProperties") and obj.BIMObjectProperties.ifc_definition_id:
global_ids.append("#" + self.file.by_id(obj.BIMObjectProperties.ifc_definition_id).GlobalId)
context.scene.CsvProperties.ifc_selector = "|".join(global_ids)
element = tool.Ifc.get_entity(obj)
if element:
global_id = getattr(element, "GlobalId", None)
if global_id:
global_ids.append(global_id)
context.scene.CsvProperties.ifc_selector = ",".join(global_ids)
return {"FINISHED"}
@@ -45,5 +45,14 @@ class CsvProperties(PropertyGroup):
name="IFC CSV Delimiter",
default=",",
)
format: EnumProperty(
items=[
("csv", "csv", ""),
("xlsx", "xlsx", ""),
("ods", "ods", ""),
],
name="Output format",
default="csv",
)
csv_custom_delimiter: StringProperty(default="", name="Custom Delimiter")
should_load_from_memory: BoolProperty(default=False, name="Load from Memory")
+11 -7
View File
@@ -21,13 +21,13 @@ from blenderbim.bim.ifc import IfcStore
class BIM_PT_ifccsv(Panel):
bl_label = "IFC CSV Import/Export"
bl_label = "CSV Import/Export"
bl_idname = "BIM_PT_ifccsv"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_collaboration"
bl_parent_id = "BIM_PT_tab_collaboration"
def draw(self, context):
layout = self.layout
@@ -60,17 +60,21 @@ class BIM_PT_ifccsv(Panel):
row = layout.row(align=True)
row.prop(attribute, "name", text="")
row.operator("bim.remove_csv_attribute", icon="X", text="").index = index
row = layout.row(align=True)
row.prop(props, "csv_delimiter")
row.prop(props, "format")
if props.csv_delimiter == "CUSTOM":
if props.format == 'csv':
row = layout.row(align=True)
row.prop(props, "csv_custom_delimiter")
row.prop(props, "csv_delimiter")
if props.csv_delimiter == "CUSTOM":
row = layout.row(align=True)
row.prop(props, "csv_custom_delimiter")
row = layout.row()
split = row.split(factor=0.5)
c = split.column()
c.operator("bim.export_ifccsv", icon="EXPORT")
c.operator("bim.export_ifccsv", icon="EXPORT", text="Export IFC to " + props.format.upper())
c = split.column()
c.operator("bim.import_ifccsv", icon="IMPORT")
@@ -20,6 +20,7 @@ import bpy
from . import ui, prop, operator
classes = (
operator.CopyDebugInformation,
operator.CreateAllShapes,
operator.CreateShapeFromStepId,
operator.InspectFromObject,
@@ -20,6 +20,9 @@ import os
import bpy
import time
import logging
import platform
import subprocess
import addon_utils
import ifcopenshell
import ifcopenshell.util.placement
import ifcopenshell.util.representation
@@ -31,6 +34,58 @@ import blenderbim.tool as tool
from blenderbim.bim.ifc import IfcStore
class CopyDebugInformation(bpy.types.Operator):
bl_idname = "bim.copy_debug_information"
bl_label = "Copy Debug Information"
bl_description = "Copies debugging information to your clipboard for use in bugreports"
def execute(self, context):
version = ".".join(
[
str(x)
for x in [
addon.bl_info.get("version", (-1, -1, -1))
for addon in addon_utils.modules()
if addon.bl_info["name"] == "BlenderBIM"
][0]
]
)
info = {
"os": platform.system(),
"os_version": platform.version(),
"python_version": platform.python_version(),
"architecture": platform.architecture(),
"machine": platform.machine(),
"processor": platform.processor(),
"blender_version": bpy.app.version_string,
"blenderbim_version": version,
"ifc": False,
}
if tool.Ifc.get():
info.update(
{
"ifc": os.path.basename(tool.Ifc.get_path()) if os.path.isfile(tool.Ifc.get_path()) else "Unsaved",
"schema": tool.Ifc.get().schema,
"preprocessor_version": tool.Ifc.get().wrapped_data.header.file_name.preprocessor_version,
"originating_system": tool.Ifc.get().wrapped_data.header.file_name.originating_system,
}
)
# Format it in a readable way
text = "\n".join(f"{k}: {v}" for k, v in info.items())
print(text)
if platform.system() == "Windows":
command = "echo | set /p nul=" + text.strip()
elif platform.system() == "Darwin": # for MacOS
command = 'printf "' + text.strip().replace("\n", "\\n").replace('"', "") + '" | pbcopy'
else: # Linux
command = 'printf "' + text.strip().replace("\n", "\\n").replace('"', "") + '" | xclip -selection clipboard'
subprocess.run(command, shell=True, check=True)
return {"FINISHED"}
class PrintIfcFile(bpy.types.Operator):
bl_idname = "bim.print_ifc_file"
bl_label = "Print IFC File"
@@ -66,14 +121,14 @@ class PurgeIfcLinks(bpy.types.Operator):
class ConvertToBlender(bpy.types.Operator):
bl_idname = "bim.converttoblender"
bl_idname = "bim.convert_to_blender"
bl_label = "Convert To Blender File"
bl_description = "Removes all IFC data, and converts the file to a simple Blender file."
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
for o in bpy.data.objects:
if o.type in {'MESH', 'EMPTY'}:
if o.type in {"MESH", "EMPTY"}:
o.BIMObjectProperties.ifc_definition_id = 0
if o.data:
o.data.BIMMeshProperties.ifc_definition_id = 0
@@ -135,6 +190,8 @@ class CreateAllShapes(bpy.types.Operator):
total = len(elements)
settings = ifcopenshell.geom.settings()
settings_2d = ifcopenshell.geom.settings()
settings_2d.set(settings_2d.INCLUDE_CURVES, True)
failures = []
excludes = () # For the developer to debug with
for i, element in enumerate(elements):
@@ -142,8 +199,16 @@ class CreateAllShapes(bpy.types.Operator):
continue
print(f"{i}/{total}:", element)
start = time.time()
shape = None
try:
shape = ifcopenshell.geom.create_shape(settings, element)
except:
try:
shape = ifcopenshell.geom.create_shape(settings_2d, element)
except:
failures.append(element)
print("***** FAILURE *****")
if shape:
print(
"Success",
time.time() - start,
@@ -151,9 +216,6 @@ class CreateAllShapes(bpy.types.Operator):
len(shape.geometry.edges),
len(shape.geometry.faces),
)
except:
failures.append(element)
print("***** FAILURE *****")
print(f"Failures: {len(failures)}")
for failure in failures:
print(failure)
@@ -166,6 +228,7 @@ class CreateShapeFromStepId(bpy.types.Operator):
bl_description = "Recreate a mesh object from a STEP ID"
bl_options = {"REGISTER", "UNDO"}
should_include_curves: bpy.props.BoolProperty()
step_id: bpy.props.IntProperty(default=0)
@classmethod
def poll(cls, context):
@@ -178,7 +241,7 @@ class CreateShapeFromStepId(bpy.types.Operator):
logger = logging.getLogger("ImportIFC")
self.ifc_import_settings = import_ifc.IfcImportSettings.factory(context, IfcStore.path, logger)
self.file = IfcStore.get_file()
element = self.file.by_id(int(context.scene.BIMDebugProperties.step_id))
element = self.file.by_id(self.step_id or int(context.scene.BIMDebugProperties.step_id))
settings = ifcopenshell.geom.settings()
if self.should_include_curves:
settings.set(settings.INCLUDE_CURVES, True)
@@ -316,10 +379,30 @@ class InspectFromObject(bpy.types.Operator):
class PrintObjectPlacement(bpy.types.Operator):
bl_idname = "bim.print_object_placement"
bl_label = "Print Object Placement"
bl_options = {"REGISTER", "UNDO"}
bl_description = (
"Print object placement to the system console.\n\n" + "ALT+CLICK create an empty object at that position"
)
step_id: bpy.props.IntProperty()
create_empty_object: bpy.props.BoolProperty(name="Create Empty Object", default=False, options={"SKIP_SAVE"})
arrow_size: bpy.props.FloatProperty(name="Arrow Size", default=0.2, subtype="DISTANCE")
def invoke(self, context, event):
# keep the viewport position on alt+click
# make sure to use SKIP_SAVE on property, otherwise it might get stuck
if event.type == "LEFTMOUSE" and event.alt:
self.create_empty_object = True
return self.execute(context)
def execute(self, context):
print(ifcopenshell.util.placement.get_local_placement(IfcStore.get_file().by_id(self.step_id)))
placement = ifcopenshell.util.placement.get_local_placement(IfcStore.get_file().by_id(self.step_id))
if self.create_empty_object:
bpy.ops.object.empty_add(type="ARROWS")
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
context.active_object.matrix_world = placement.transpose()
context.active_object.matrix_world.translation *= si_conversion
context.active_object.empty_display_size = self.arrow_size
print(placement)
return {"FINISHED"}
@@ -21,13 +21,13 @@ from bpy.types import Panel
class BIM_PT_debug(Panel):
bl_label = "IFC Debug"
bl_label = "Debug"
bl_idname = "BIM_PT_debug"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_quality_control"
bl_parent_id = "BIM_PT_tab_quality_control"
def draw(self, context):
layout = self.layout
@@ -48,6 +48,9 @@ class BIM_PT_debug(Panel):
row = layout.row()
row.operator("bim.print_ifc_file")
row = layout.row()
row.operator("bim.copy_debug_information")
row = layout.row()
row.operator("bim.purge_hdf5_cache")
@@ -55,7 +58,10 @@ class BIM_PT_debug(Panel):
row.operator("bim.purge_ifc_links")
row = layout.row()
row.operator("bim.converttoblender")
row.operator("bim.update_representation", text="Manually Save Representation")
row = layout.row()
row.operator("bim.convert_to_blender")
row = layout.row()
row.operator("bim.create_all_shapes")
@@ -77,6 +83,16 @@ class BIM_PT_debug(Panel):
).percentile = context.scene.BIMDebugProperties.percentile_of_polygons
row.prop(props, "percentile_of_polygons", text="")
if context.active_object and context.active_object.data:
mprops = context.active_object.data.BIMMeshProperties
row = layout.row()
row.operator("bim.get_representation_ifc_parameters")
for index, ifc_parameter in enumerate(mprops.ifc_parameters):
row = layout.row(align=True)
row.prop(ifc_parameter, "name", text="")
row.prop(ifc_parameter, "value", text="")
row.operator("bim.update_parametric_representation", icon="FILE_REFRESH", text="").index = index
layout.label(text="Inspector:")
row = layout.row(align=True)
@@ -24,13 +24,13 @@ import json
class BIM_PT_diff(Panel):
bl_label = "IFC Diff"
bl_label = "Diff"
bl_idname = "BIM_PT_diff"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_quality_control"
bl_parent_id = "BIM_PT_tab_quality_control"
def draw(self, context):
if not DiffData.is_loaded:
@@ -23,7 +23,7 @@ from blenderbim.bim.module.document.data import DocumentData, ObjectDocumentData
class BIM_PT_documents(Panel):
bl_label = "IFC Documents"
bl_label = "Documents"
bl_idname = "BIM_PT_documents"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
@@ -76,14 +76,14 @@ class BIM_PT_documents(Panel):
class BIM_PT_object_documents(Panel):
bl_label = "IFC Documents"
bl_label = "Documents"
bl_idname = "BIM_PT_object_documents"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "object"
bl_order = 1
bl_parent_id = "BIM_PT_misc_object"
bl_parent_id = "BIM_PT_tab_misc"
@classmethod
def poll(cls, context):
@@ -24,22 +24,27 @@ classes = (
operator.ActivateDrawingStyle,
operator.ActivateModel,
operator.AddAnnotation,
operator.AddAnnotationType,
operator.AddDrawing,
operator.AddDrawingStyle,
operator.AddDrawingStyleAttribute,
operator.AddDrawingToSheet,
operator.AddReference,
operator.AddReferenceToSheet,
operator.AddSchedule,
operator.AddScheduleToSheet,
operator.AddReferenceToSheet,
operator.AddSheet,
operator.AddTextLiteral,
operator.BuildSchedule,
operator.CleanWireframes,
operator.ContractSheet,
operator.ContractTargetView,
operator.CreateDrawing,
operator.CreateSheets,
operator.DisableAddAnnotationType,
operator.DisableEditingAssignedProduct,
operator.DisableEditingDrawings,
operator.DisableEditingReferences,
operator.DisableEditingSchedules,
operator.DisableEditingSheets,
operator.DisableEditingText,
@@ -48,34 +53,34 @@ classes = (
operator.EditSheet,
operator.EditText,
operator.EditTextPopup,
operator.EnableAddAnnotationType,
operator.EnableEditingAssignedProduct,
operator.EnableEditingText,
operator.ExpandSheet,
operator.ExpandTargetView,
operator.LoadDrawings,
operator.LoadReferences,
operator.LoadSchedules,
operator.LoadSheets,
operator.OpenDrawing,
operator.OpenReference,
operator.OpenSchedule,
operator.OpenSheet,
operator.ReloadDrawingStyles,
operator.RemoveDrawing,
operator.RemoveDrawingFromSheet,
operator.RemoveDrawingStyle,
operator.RemoveDrawingStyleAttribute,
operator.RemoveReference,
operator.RemoveSchedule,
operator.ReloadDrawingStyles,
operator.RemoveSheet,
operator.RemoveTextLiteral,
operator.SelectAllDrawings,
operator.ResizeText,
operator.SaveDrawingStyle,
operator.SaveDrawingStylesData,
operator.SelectAllDrawings,
operator.SelectAssignedProduct,
operator.SelectDocIfcFile,
operator.LoadReferences,
operator.DisableEditingReferences,
operator.AddReference,
operator.RemoveReference,
operator.OpenReference,
prop.Variable,
prop.Drawing,
prop.Document,
@@ -89,7 +94,6 @@ classes = (
prop.BIMAnnotationProperties,
ui.BIM_PT_camera,
ui.BIM_PT_drawing_underlay,
ui.BIM_PT_annotation_utilities,
ui.BIM_PT_sheets,
ui.BIM_PT_drawings,
ui.BIM_PT_schedules,
@@ -103,13 +107,14 @@ classes = (
gizmos.DimensionLabelGizmo,
gizmos.ExtrusionGuidesGizmo,
gizmos.ExtrusionWidget,
workspace.LaunchAnnotationTypeManager,
workspace.Hotkey,
)
def register():
if not bpy.app.background:
bpy.utils.register_tool(workspace.AnnotationTool, after={"bim.bim_tool"}, separator=False, group=True)
bpy.utils.register_tool(workspace.AnnotationTool, after={"bim.bim_tool"}, separator=True, group=False)
bpy.types.Scene.DocProperties = bpy.props.PointerProperty(type=prop.DocProperties)
bpy.types.Scene.BIMAnnotationProperties = bpy.props.PointerProperty(type=prop.BIMAnnotationProperties)
bpy.types.Camera.BIMCameraProperties = bpy.props.PointerProperty(type=prop.BIMCameraProperties)
@@ -152,6 +152,7 @@ class Annotator:
def get_placeholder_coords(camera=None):
if not camera:
camera = bpy.context.scene.camera
z_offset = camera.matrix_world.to_quaternion() @ Vector((0, 0, -1))
y = camera.data.ortho_scale / 4
@@ -162,9 +163,14 @@ class Annotator:
y_offset = camera.matrix_world.to_quaternion() @ Vector((0, y, 0))
x_offset = camera.matrix_world.to_quaternion() @ Vector((y / 2, 0, 0))
center = camera.matrix_world.inverted() @ bpy.context.scene.cursor.location
center.z = 0
center = camera.matrix_world @ center
return (
camera.location + z_offset,
camera.location + z_offset + y_offset,
camera.location + z_offset + x_offset,
camera.location + z_offset + x_offset + y_offset,
center + z_offset,
center + z_offset + y_offset,
center + z_offset + x_offset,
center + z_offset + x_offset + y_offset,
)
@@ -287,28 +287,36 @@ class DecoratorData:
cls.data[obj.name] = text_data
return text_data
# used by Ifc Annotations with ObjectType = "DIMENSION" / "DIAMETER"
@classmethod
def get_dimension_data(cls, obj):
"""used by Ifc Annotations with ObjectType:
DIMENSION / DIAMETER / SECTION_LEVEL / PLAN_LEVEL / RADIUS
"""
result = cls.data.get(obj.name, None)
if result is not None:
return result
element = tool.Ifc.get_entity(obj)
supported_object_types = ("DIMENSION", "DIAMETER")
supported_object_types = ("DIMENSION", "DIAMETER", "SECTION_LEVEL", "PLAN_LEVEL", "RADIUS")
if not element or not element.is_a("IfcAnnotation") or element.ObjectType not in supported_object_types:
return None
dimension_style = "arrow"
fill_bg = False
classes = ifcopenshell.util.element.get_pset(element, "EPset_Annotation", "Classes")
if classes and "oblique" in classes.lower().split():
dimension_style = "oblique"
if classes:
classes_split = classes.lower().split()
if "oblique" in classes_split:
dimension_style = "oblique"
elif "fill-bg" in classes_split:
fill_bg = True
pset_data = ifcopenshell.util.element.get_pset(element, "BBIM_Dimension") or {}
show_description_only = pset_data.get("ShowDescriptionOnly", False)
suppress_zero_inches = pset_data.get("SuppressZeroInches", False)
text_prefix = pset_data.get("TextPrefix", "")
text_suffix = pset_data.get("TextSuffix", "")
text_prefix = pset_data.get("TextPrefix", None) or ""
text_suffix = pset_data.get("TextSuffix", None) or ""
dimension_data = {
"dimension_style": dimension_style,
@@ -316,6 +324,7 @@ class DecoratorData:
"suppress_zero_inches": suppress_zero_inches,
"text_prefix": text_prefix,
"text_suffix": text_suffix,
"fill_bg": fill_bg,
}
cls.data[obj.name] = dimension_data
return dimension_data
@@ -329,18 +338,31 @@ class AnnotationData:
def load(cls):
cls.is_loaded = True
cls.props = bpy.context.scene.BIMAnnotationProperties
cls.data["relating_types"] = cls.get_relating_types()
cls.data["relating_type_id"] = cls.relating_type_id()
cls.data["relating_types"] = cls.relating_types()
@classmethod
def get_relating_types(cls):
def relating_type_id(cls):
object_type = cls.props.object_type
relating_types = []
for relating_type in tool.Ifc.get().by_type("IfcTypeProduct"):
if tool.Drawing.is_annotation_object_type(relating_type, object_type):
relating_types.append(relating_type)
enum_items = [(str(e.id()), e.Name or "Unnamed", e.Description or "") for e in relating_types]
results = [("0", "Untyped", "")]
results.extend([(str(e.id()), e.Name or "Unnamed", e.Description or "") for e in relating_types])
return results
# item to create anootations without relating types
enum_items.insert(0, ("0", "-", ""))
return enum_items
@classmethod
def relating_types(cls):
object_type = cls.props.object_type
relating_types = []
for relating_type in tool.Ifc.get().by_type("IfcTypeProduct"):
if tool.Drawing.is_annotation_object_type(relating_type, object_type):
relating_types.append({
"id": relating_type.id(),
"name": relating_type.Name or "Unnamed",
"description": relating_type.Description or "No Description",
})
return sorted(relating_types, key=lambda x: x["name"])
@@ -248,8 +248,13 @@ class BaseDecorator:
if check_mode and obj.data.is_editmode:
return self.get_editmesh_geom(obj)
vertices = [obj.matrix_world @ v.co for v in obj.data.vertices]
indices = [e.vertices for e in obj.data.edges]
bm = bmesh.new()
bm.from_mesh(obj.data)
vertices = [obj.matrix_world @ v.co for v in bm.verts]
# In object mode, it's nicer to not show "internal edges". Most will be dissolved anyway.
indices = [[v.index for v in e.verts] for e in bm.edges if len(e.link_faces) != 2]
bm.free()
return vertices, indices
def get_editmesh_geom(self, obj):
@@ -363,7 +368,7 @@ class BaseDecorator:
"""
if multiline:
line_no = 0
line_no = line_no
lines = text.split("\\n")
lines = lines if multiline_to_bottom else lines[::-1]
for line in lines:
@@ -454,6 +459,24 @@ class BaseDecorator:
blf.draw(font_id, text)
blf.disable(font_id, blf.ROTATION)
def draw_dimension_text(self, context, get_text, description, dimension_data, **draw_label_kwargs):
prefix = dimension_data["text_prefix"]
suffix = dimension_data["text_suffix"]
show_description_only = dimension_data["show_description_only"]
line_number_start = 0
if not show_description_only:
text = get_text()
full_prefix = ((description + "\\n") if description else "") + prefix
text = full_prefix + text + suffix
line_number_start -= full_prefix.count("\\n")
else:
if not description:
return
text = description
self.draw_label(context, text=text, line_no=line_number_start, multiline=True, **draw_label_kwargs)
@lru_cache(maxsize=None)
def format_value(self, context, value):
drawing_pset_data = DrawingsData.data["active_drawing_pset_data"]
@@ -502,7 +525,11 @@ class BaseDecorator:
return matrix.inverted()[i].to_3d().normalized()
text_dir_world_x_axis = get_basis_vector(obj.matrix_world)
text_dir = (camera.matrix_world.inverted().to_quaternion() @ text_dir_world_x_axis).to_2d().normalized()
camera_matrix = camera.matrix_world.copy()
camera_matrix[0][0] = 1
camera_matrix[1][1] = 1
camera_matrix[2][2] = 1
text_dir = (camera_matrix.inverted().to_quaternion() @ text_dir_world_x_axis).to_2d().normalized()
pos = location_3d_to_region_2d(region, region3d, text_world_position)
props = obj.BIMTextProperties
@@ -637,39 +664,35 @@ class DimensionDecorator(BaseDecorator):
perpendicular = Vector((-text_dir.y, text_dir.x)).normalized()
text_offset = perpendicular * text_offset_value
common_label_attrs = {
"context": context,
"multiline": True,
"text_dir": text_dir,
}
base_pos = p0 + text_dir * 0.5
if not show_description_only:
length = (v1 - v0).length
text = self.format_value(context, length)
if isinstance(self, DiameterDecorator):
text = "D" + text
text = text_prefix + text + text_suffix
else:
if not description:
continue
text = description
self.draw_label(
context,
text,
p0 + text_dir * 0.5 + text_offset,
text_dir,
box_alignment="bottom-middle",
multiline=True,
multiline_to_bottom=False,
)
if description:
self.draw_label(
context,
description,
p0 + text_dir * 0.5 - text_offset,
text_dir,
box_alignment="top-middle",
multiline=True,
)
self.draw_label(
text=text,
pos=base_pos + text_offset,
box_alignment="bottom-middle",
multiline_to_bottom=False,
**common_label_attrs,
)
elif show_description_only and description:
if not show_description_only and description:
self.draw_label(
context,
description,
p0 + text_dir * 0.5 + text_offset,
text_dir,
box_alignment="bottom-middle",
multiline=True,
multiline_to_bottom=False,
text=description, pos=base_pos - text_offset, box_alignment="top-middle", **common_label_attrs
)
@@ -734,7 +757,7 @@ class AngleDecorator(BaseDecorator):
arcs_color = None
edges_color = UNSPECIAL_ELEMENT_COLOR
if context.object == obj and obj.data.is_editmode:
if context.active_object == obj and obj.data.is_editmode:
arcs_color = context.preferences.addons["blenderbim"].preferences.decorator_color_special
edges_color = None
@@ -772,19 +795,23 @@ class AngleDecorator(BaseDecorator):
v0 = Vector(vertices[i0])
v1 = Vector(vertices[i1])
v2 = Vector(vertices[i1 + 1])
p0 = location_3d_to_region_2d(region, region3d, v0)
p1 = location_3d_to_region_2d(region, region3d, v1)
p2 = location_3d_to_region_2d(region, region3d, v2)
edge0 = p0 - p1
edge1 = p2 - p1
# calculate angle value
edge0_ws = v0 - v1
edge1_ws = v2 - v1
try:
cos_a = edge0.dot(edge1) / (edge0.length * edge1.length)
cos_a = edge0_ws.dot(edge1_ws) / (edge0_ws.length * edge1_ws.length)
except ZeroDivisionError:
continue
circle_angle_rad = acos(cos_a)
circle_angle = circle_angle_rad / pi * 180
# calculate angle position
p0 = location_3d_to_region_2d(region, region3d, v0)
p1 = location_3d_to_region_2d(region, region3d, v1)
p2 = location_3d_to_region_2d(region, region3d, v2)
edge0 = p0 - p1
edge1 = p2 - p1
base_edge = edge0 if ccw(p0, p1, p2) else edge1
text_offset = (Matrix.Rotation(-circle_angle_rad / 2, 2) @ base_edge).normalized() * ANGLE_LABEL_OFFSET
label_position = p1 + text_offset
@@ -824,12 +851,15 @@ class RadiusDecorator(BaseDecorator):
objecttype = "RADIUS"
def get_spline_end(self, obj):
def get_spline_points(self, obj):
spline = obj.data.splines[0]
spline_points = spline.bezier_points if spline.bezier_points else spline.points
if not spline_points:
return Vector((0, 0, 0))
return obj.matrix_world @ spline_points[0].co
spline_points = [Vector([0, 0, 0])] * 2
elif len(spline_points) == 1:
spline_points = [spline_points[0]] * 2
return [(obj.matrix_world @ p.co) for p in spline_points[:2]]
def decorate(self, context, obj):
self.draw_arrow(context, obj)
@@ -838,15 +868,24 @@ class RadiusDecorator(BaseDecorator):
def draw_labels(self, context, obj):
region = context.region
region3d = context.region_data
dir = Vector((1, 0))
pos = location_3d_to_region_2d(region, region3d, self.get_spline_end(obj))
spline_points = self.get_spline_points(obj)
spline = obj.data.splines[0]
spline_points = spline.bezier_points if spline.bezier_points else spline.points
if spline_points:
length = (spline_points[-1].co - spline_points[-2].co).length
text = self.format_value(context, length)
self.draw_label(context, text, pos, dir, gap=0, center=False, vcenter=False)
p0, p1 = [location_3d_to_region_2d(region, region3d, p) for p in self.get_spline_points(obj)]
element = tool.Ifc.get_entity(obj)
description = element.Description
dimension_data = DecoratorData.get_dimension_data(obj)
viewportDrawingScale = self.get_viewport_drawing_scale(context)
text_offset = 20 * viewportDrawingScale
pos = p0 - (p1 - p0).normalized() * text_offset
def get_text():
length = (spline_points[-1] - spline_points[-2]).length
return "R" + self.format_value(context, length)
self.draw_dimension_text(
context, get_text, description, dimension_data, pos=pos, text_dir=Vector((1, 0)), box_alignment="center"
)
class FallDecorator(BaseDecorator):
@@ -1056,23 +1095,40 @@ class PlanLevelDecorator(BaseDecorator):
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
region = context.region
region3d = context.region_data
element = tool.Ifc.get_entity(obj)
description = element.Description
dimension_data = DecoratorData.get_dimension_data(obj)
for verts in splines:
p0, p1 = [location_3d_to_region_2d(region, region3d, v) for v in verts[:2]]
dir = p1 - p0
if dir.length < 1:
text_dir = p1 - p0
if text_dir.length < 1:
continue
if dir.x > 0:
if text_dir.x > 0:
box_alignment = "bottom-left"
else:
box_alignment = "bottom-right"
dir *= -1
text_dir *= -1
z = verts[0].z / unit_scale
z = ifcopenshell.util.geolocation.auto_z2e(tool.Ifc.get(), z)
z *= unit_scale
text = "RL " + self.format_value(context, z)
self.draw_label(context, text, p0, dir, gap=8, center=False, box_alignment=box_alignment)
def get_text():
z = verts[0].z
rl = self.format_value(context, z)
text = "{}{}".format("" if z < 0 else "+", rl)
return text
self.draw_dimension_text(
context,
get_text,
description,
dimension_data,
box_alignment=box_alignment,
pos=p0,
text_dir=text_dir,
gap=8,
center=False,
)
class SectionLevelDecorator(BaseDecorator):
@@ -1119,31 +1175,29 @@ class SectionLevelDecorator(BaseDecorator):
self.draw_labels(context, obj, self.get_splines(obj), text_position.to_2d(), text_dir.to_2d())
def draw_labels(self, context, obj, splines, text_position, text_dir):
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
element = tool.Ifc.get_entity(obj)
storey = tool.Drawing.get_annotation_element(element)
tag = storey.Name if storey else element.Description
dimension_data = DecoratorData.get_dimension_data(obj)
for verts in splines:
z = verts[0].z / unit_scale
z = ifcopenshell.util.geolocation.auto_z2e(tool.Ifc.get(), z)
z *= unit_scale
text_lines = ["RL " + self.format_value(context, z)]
if tag:
text_lines.append(tag)
def get_text():
z = verts[0].z
rl = self.format_value(context, z)
text = "RL {}{}".format("" if z < 0 else "+", rl)
return text
for line_i, line in zip((0, -1), text_lines):
self.draw_label(
context,
line,
text_position,
text_dir,
center=False,
vcenter=False,
line_no=line_i,
)
self.draw_dimension_text(
context,
get_text,
tag,
dimension_data,
box_alignment="bottom-left",
pos=text_position,
text_dir=text_dir,
)
break # support only 1 label
@@ -1917,7 +1971,12 @@ class DecorationsHandler:
results.append((obj, dec))
elif isinstance(obj.data, bpy.types.Mesh):
results.append((obj, self.decorators["MISC"]))
if object_type == "LINEWORK" and "dashed" in str(
ifcopenshell.util.element.get_pset(element, "EPset_Annotation", "Classes")
).split(" "):
results.append((obj, self.decorators["HIDDEN_LINE"]))
else:
results.append((obj, self.decorators["MISC"]))
return results
@@ -354,7 +354,7 @@ class UglyDotGizmo(OffsetHandle, types.Gizmo):
def refresh(self):
offset = self.target_get_value("offset") / self.scale_value
self.matrix_offset.col[3][2] = offset # z-shift
self.matrix_offset.translation.z = offset # z-shift
def draw(self, ctx):
self.refresh()
@@ -385,7 +385,7 @@ class DotGizmo(CustomGizmo, OffsetHandle, types.Gizmo):
def refresh(self):
offset = self.target_get_value("offset") / self.scale_value
self.matrix_offset.col[3][2] = offset # z-shifted
self.matrix_offset.translation.z = offset # z-shifted
def draw(self, ctx):
self.refresh()
@@ -443,7 +443,7 @@ class DimensionLabelGizmo(types.Gizmo):
def refresh(self, ctx):
value = self.target_get_value("value")
self.matrix_offset.col[3][2] = value * 0.5
self.matrix_offset.translation.z = value * 0.5
unit_system = ctx.scene.unit_settings.system
self.text_label = bpy.utils.units.to_string(unit_system, "LENGTH", value, 3, split_unit=False)
@@ -192,6 +192,8 @@ def format_distance(
tx_dist += str(feet) + "'"
if feet and add_inches:
tx_dist += " - "
if not feet and value < 0:
tx_dist += "-"
if add_inches:
tx_dist += str(inches)
if add_inches and frac:
@@ -208,19 +210,19 @@ def format_distance(
if precision:
value = precision * round(float(value) / precision)
if decimal_places:
if decimal_places is not None:
fmt = "%1." + str(decimal_places) + "f"
# Meters
if unit_length == "METERS":
if not decimal_places:
if decimal_places is None:
fmt = "%1.3f"
if hide_units is False:
fmt += " m"
tx_dist = fmt % value
# Centimeters
elif unit_length == "CENTIMETERS":
if not decimal_places:
if decimal_places is None:
fmt = "%1.1f"
if hide_units is False:
fmt += " cm"
@@ -228,7 +230,7 @@ def format_distance(
tx_dist = fmt % d_cm
# Millimeters
elif unit_length == "MILLIMETERS":
if not decimal_places:
if decimal_places is None:
fmt = "%1.0f"
if hide_units is False:
fmt += " mm"
@@ -237,20 +239,20 @@ def format_distance(
# Otherwise Use Adaptive Units
else:
if round(value, 2) >= 1.0 and not decimal_places:
if round(value, 2) >= 1.0 and decimal_places is None:
fmt = "%1.3f"
if hide_units is False:
fmt += " m"
tx_dist = fmt % value
else:
if round(value, 2) >= 0.01 and not decimal_places:
if round(value, 2) >= 0.01 and decimal_places is None:
fmt = "%1.1f"
if hide_units is False:
fmt += " cm"
d_cm = value * (100)
tx_dist = fmt % d_cm
else:
if not decimal_places:
if decimal_places is None:
fmt = "%1.0f"
if hide_units is False:
fmt += " mm"
@@ -23,6 +23,7 @@ import json
import time
import bmesh
import shutil
import hashlib
import shapely
import subprocess
import webbrowser
@@ -32,6 +33,7 @@ import ifcopenshell
import ifcopenshell.geom
import ifcopenshell.util.selector
import ifcopenshell.util.representation
import ifcopenshell.util.element
import blenderbim.bim.schema
import blenderbim.tool as tool
import blenderbim.core.drawing as core
@@ -84,6 +86,52 @@ class Operator:
return {"FINISHED"}
class AddAnnotationType(bpy.types.Operator, Operator):
bl_idname = "bim.add_annotation_type"
bl_label = "Add Annotation Type"
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
props = context.scene.BIMAnnotationProperties
object_type = props.object_type
has_representation = props.create_representation_for_type
drawing = tool.Ifc.get_entity(bpy.context.scene.camera)
if props.create_representation_for_type:
obj = tool.Drawing.create_annotation_object(drawing, object_type)
else:
obj = bpy.data.objects.new(object_type, None)
obj.name = props.type_name
element = tool.Drawing.run_root_assign_class(
obj=obj,
ifc_class="IfcTypeProduct",
predefined_type=object_type,
should_add_representation=has_representation,
context=ifcopenshell.util.representation.get_context(tool.Ifc.get(), "Model", "Annotation", "MODEL_VIEW"),
ifc_representation_class=tool.Drawing.get_ifc_representation_class(object_type),
)
element.ApplicableOccurrence = f"IfcAnnotation/{object_type}"
class EnableAddAnnotationType(bpy.types.Operator, Operator):
bl_idname = "bim.enable_add_annotation_type"
bl_label = "Enable Add Annotation Type"
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
bpy.context.scene.BIMAnnotationProperties.is_adding_type = True
class DisableAddAnnotationType(bpy.types.Operator, Operator):
bl_idname = "bim.disable_add_annotation_type"
bl_label = "Disable Add Annotation Type"
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
bpy.context.scene.BIMAnnotationProperties.is_adding_type = False
class AddDrawing(bpy.types.Operator, Operator):
bl_idname = "bim.add_drawing"
bl_label = "Add Drawing"
@@ -154,7 +202,7 @@ class CreateDrawing(bpy.types.Operator):
@classmethod
def poll(cls, context):
return bool(tool.Ifc.get() and tool.Drawing.is_camera_orthographic() and tool.Drawing.is_drawing_active())
return bool(tool.Ifc.get() and tool.Drawing.is_drawing_active())
def invoke(self, context, event):
# printing all drawings on shift+click
@@ -168,7 +216,7 @@ class CreateDrawing(bpy.types.Operator):
if self.print_all:
original_drawing_id = self.props.active_drawing_id
drawings_to_print = [d.ifc_definition_id for d in self.props.drawings if d.is_selected]
drawings_to_print = [d.ifc_definition_id for d in self.props.drawings if d.is_selected and d.is_drawing]
else:
drawings_to_print = [self.props.active_drawing_id]
@@ -184,7 +232,8 @@ class CreateDrawing(bpy.types.Operator):
with profile("Drawing generation process"):
with profile("Initialize drawing generation process"):
self.cprops = self.camera.data.BIMCameraProperties
self.drawing_name = self.file.by_id(drawing_id).Name
self.drawing = self.file.by_id(drawing_id)
self.drawing_name = self.drawing.Name
self.metadata = tool.Drawing.get_drawing_metadata(self.camera_element)
self.get_scale(context)
if self.cprops.update_representation(self.camera):
@@ -202,14 +251,20 @@ class CreateDrawing(bpy.types.Operator):
self.svg_writer.setup_drawing_resource_paths(self.camera_element)
underlay_svg = None
linework_svg = None
annotation_svg = None
with profile("Generate underlay"):
underlay_svg = self.generate_underlay(context)
with profile("Generate linework"):
linework_svg = self.generate_linework(context)
if tool.Drawing.is_camera_orthographic():
linework_svg = self.generate_linework(context)
with profile("Generate annotation"):
annotation_svg = self.generate_annotation(context)
if tool.Drawing.is_camera_orthographic():
annotation_svg = self.generate_annotation(context)
with profile("Combine SVG layers"):
svg_path = self.combine_svgs(context, underlay_svg, linework_svg, annotation_svg)
@@ -275,7 +330,7 @@ class CreateDrawing(bpy.types.Operator):
return svg_path
def generate_underlay(self, context):
if not self.cprops.has_underlay:
if not ifcopenshell.util.element.get_pset(self.drawing, "EPset_Drawing", "HasUnderlay"):
return
svg_path = self.get_svg_path(cache_type="underlay")
context.scene.render.filepath = svg_path[0:-4] + ".png"
@@ -317,7 +372,7 @@ class CreateDrawing(bpy.types.Operator):
return svg_path
def generate_linework(self, context):
if not self.cprops.has_linework:
if not ifcopenshell.util.element.get_pset(self.drawing, "EPset_Drawing", "HasLinework"):
return
svg_path = self.get_svg_path(cache_type="linework")
if os.path.isfile(svg_path) and self.props.should_use_linework_cache:
@@ -346,10 +401,6 @@ class CreateDrawing(bpy.types.Operator):
}
cached_linework -= edited_guids
# This is a work in progress. See #1153 and #1564.
import hashlib
import ifcopenshell.draw
files = {context.scene.BIMProperties.ifc_file: tool.Ifc.get()}
for ifc_path, ifc in files.items():
@@ -434,7 +485,7 @@ class CreateDrawing(bpy.types.Operator):
drawing_elements = tool.Drawing.get_drawing_elements(self.camera_element)
self.setup_serialiser(ifc)
self.setup_serialiser(ifc, target_view)
cache = IfcStore.get_cache()
[cache.remove(guid) for guid in invalidated_guids]
tree = ifcopenshell.geom.tree()
@@ -520,7 +571,12 @@ class CreateDrawing(bpy.types.Operator):
for projection in projections:
boundary_lines = []
for path in projection.findall("./{http://www.w3.org/2000/svg}path"):
start, end = [co[1:].split(",") for co in path.attrib["d"].split()]
# Rounding is necessary to ensure coincident points are coincident
start, end = [[round(float(o), 1) for o in co[1:].split(",")] for co in path.attrib["d"].split()]
if start == end:
continue
# Extension by 0.5mm is necessary to ensure lines overlap with other diagonal lines
start, end = tool.Drawing.extend_line(start, end, 0.5)
boundary_lines.append(shapely.LineString([start, end]))
unioned_boundaries = shapely.union_all(shapely.GeometryCollection(boundary_lines))
closed_polygons = shapely.polygonize(unioned_boundaries.geoms)
@@ -542,6 +598,9 @@ class CreateDrawing(bpy.types.Operator):
e
for e in tree.select_ray(self.pythonize(a), self.pythonize(b - a))
if not e.instance.is_a("IfcAnnotation")
and tool.Cad.is_point_on_edge(
Vector(list(e.position)), (Vector(self.pythonize(a)), Vector(self.pythonize(b)))
)
]
if elements:
path = etree.Element("path")
@@ -770,7 +829,7 @@ class CreateDrawing(bpy.types.Operator):
return svg_path
def setup_serialiser(self, ifc):
def setup_serialiser(self, ifc, target_view):
self.svg_settings = ifcopenshell.geom.settings(
DISABLE_TRIANGULATION=True, STRICT_TOLERANCE=True, INCLUDE_CURVES=True
)
@@ -793,6 +852,9 @@ class CreateDrawing(bpy.types.Operator):
self.serialiser.setElevationRefGuid(self.camera_element.GlobalId)
self.serialiser.setScale(self.scale)
self.serialiser.setSubtractionSettings(ifcopenshell.ifcopenshell_wrapper.ALWAYS)
self.serialiser.setUsePrefiltering(True) # See #3359
if target_view == "REFLECTED_PLAN_VIEW":
self.serialiser.setMirrorY(True)
# tree = ifcopenshell.geom.tree()
# This instructs the tree to explode BReps into faces and return
# the style of the face when running tree.select_ray()
@@ -827,6 +889,21 @@ class CreateDrawing(bpy.types.Operator):
)
return classes
def is_manifold(self, obj):
result = self.is_manifold_cache.get(obj.data.name, None)
if result is not None:
return result
bm = bmesh.new()
bm.from_mesh(obj.data)
for edge in bm.edges:
if not edge.is_manifold:
bm.free()
self.is_manifold_cache[obj.data.name] = False
return False
self.is_manifold_cache[obj.data.name] = True
return True
def merge_linework_and_add_metadata(self, root):
join_criteria = ifcopenshell.util.element.get_pset(self.camera_element, "EPset_Drawing", "JoinCriteria")
if join_criteria:
@@ -837,6 +914,7 @@ class CreateDrawing(bpy.types.Operator):
group = root.findall(".//{http://www.w3.org/2000/svg}g")[0]
joined_paths = {}
self.is_manifold_cache = {}
ifc = tool.Ifc.get()
for el in root.findall(".//{http://www.w3.org/2000/svg}g[@{http://www.ifcopenshell.org/ns}guid]"):
@@ -846,6 +924,54 @@ class CreateDrawing(bpy.types.Operator):
classes.append("cut")
el.set("class", " ".join(classes))
obj = tool.Ifc.get_object(element)
if not self.is_manifold(obj):
continue
# An element group will contain a bunch of paths representing the
# cut of that element. However IfcOpenShell may not correctly
# create closed paths. We post-process all paths with shapely to
# ensure things that should be closed (i.e.
# shapely.polygonize_full) are, and things which aren't are left
# alone (e.g. dangles, cuts, invalids). See #3421.
line_strings = []
old_paths = []
for path in el.findall("{http://www.w3.org/2000/svg}path"):
for subpath in path.attrib["d"].split("M")[1:]:
subpath = "M" + subpath.strip()
coords = [[round(float(o), 1) for o in co[1:].split(",")] for co in subpath.split()]
line_strings.append(shapely.LineString(coords))
old_paths.append(path)
unioned_line_strings = shapely.union_all(shapely.GeometryCollection(line_strings))
if hasattr(unioned_line_strings, "geoms"):
results = shapely.polygonize_full(unioned_line_strings.geoms)
else:
results = []
# If we succeeded in generating new path geometry, remove all the
# old paths and add new ones.
if results:
for path in old_paths:
path.getparent().remove(path)
for result in results:
for geom in result.geoms:
path = etree.SubElement(el, "{http://www.w3.org/2000/svg}path")
if isinstance(geom, shapely.Polygon):
d = (
"M"
+ " L".join([",".join([str(o) for o in co]) for co in geom.exterior.coords[0:-1]])
+ " Z"
)
for interior in geom.interiors:
d += (
" M"
+ " L".join([",".join([str(o) for o in co]) for co in interior.coords[0:-1]])
+ " Z"
)
elif isinstance(geom, shapely.LineString):
d = "M" + " L".join([",".join([str(o) for o in co]) for co in geom.coords]) + " Z"
path.attrib["d"] = d
# Architectural convention only merges these objects. E.g. pipe segments and fittings shouldn't merge.
if not element.is_a("IfcWall") and not element.is_a("IfcSlab"):
continue
@@ -873,8 +999,10 @@ class CreateDrawing(bpy.types.Operator):
is_closed_polygon = False
for path in el.findall("{http://www.w3.org/2000/svg}path"):
for subpath in path.attrib["d"].split("M")[1:]:
subpath = "M" + subpath.strip()
coords = [[round(float(o), 1) for o in co[1:].split(",")] for co in subpath.split()]
subpath_co = "M" + subpath.strip(" Z")
coords = [[round(float(o), 1) for o in co[1:].split(",")] for co in subpath_co.split()]
if subpath.strip().lower().endswith("z"):
coords.append(coords[0])
if len(coords) > 2 and coords[0] == coords[-1]:
is_closed_polygon = True
polygons.append(shapely.Polygon(coords))
@@ -921,7 +1049,7 @@ class CreateDrawing(bpy.types.Operator):
group.insert(0, projection)
def generate_annotation(self, context):
if not self.cprops.has_annotation:
if not ifcopenshell.util.element.get_pset(self.drawing, "EPset_Drawing", "HasAnnotation"):
return
svg_path = self.get_svg_path(cache_type="annotation")
if os.path.isfile(svg_path) and self.props.should_use_annotation_cache:
@@ -931,7 +1059,9 @@ class CreateDrawing(bpy.types.Operator):
filtered_drawing_elements = tool.Drawing.get_drawing_elements(self.camera_element)
elements = [e for e in elements if e in filtered_drawing_elements]
annotations = sorted(elements, key=lambda a: tool.Drawing.get_annotation_z_index(a))
annotations = sorted(
elements, key=lambda a: (tool.Drawing.get_annotation_z_index(a), 1 if a.ObjectType == "TEXT" else 0)
)
precision = ifcopenshell.util.element.get_pset(self.camera_element, "EPset_Drawing", "MetricPrecision")
if not precision:
@@ -1264,7 +1394,25 @@ class ActivateModel(bpy.types.Operator):
CutDecorator.uninstall()
if not bpy.app.background:
bpy.ops.object.hide_view_clear()
view3d_context = dict()
for window in bpy.context.window_manager.windows:
screen = window.screen
for area in screen.areas:
if area.type == "VIEW_3D":
view3d_context["window"] = window
view3d_context["screen"] = screen
view3d_context["area"] = area
for region in area.regions:
if region.type == "WINDOW":
view3d_context["region"] = region
break
break
if view3d_context:
break
if view3d_context:
bpy.ops.object.hide_view_clear(view3d_context)
subcontext = ifcopenshell.util.representation.get_context(tool.Ifc.get(), "Model", "Body", "MODEL_VIEW")
@@ -1386,6 +1534,9 @@ class RemoveDrawing(bpy.types.Operator, Operator):
tool.Ifc.get().by_id(d.ifc_definition_id) for d in context.scene.DocProperties.drawings if d.is_selected
]
else:
if not self.drawing:
self.report({"ERROR"}, "No drawing selected")
return {"CANCELLED"}
drawings = [tool.Ifc.get().by_id(self.drawing)]
removed_drawings = [drawing.id() for drawing in drawings]
@@ -1632,10 +1783,14 @@ class ActivateDrawingStyle(bpy.types.Operator, Operator):
space = self.get_view_3d(context) # Do not remove. It is used in exec later
style = json.loads(self.drawing_style.raster_style)
for path, value in style.items():
if isinstance(value, str):
exec(f"{path} = '{value}'")
else:
exec(f"{path} = {value}")
try:
if isinstance(value, str):
exec(f"{path} = '{value}'")
else:
exec(f"{path} = {value}")
except:
# Differences in Blender versions mean result in failures here
print(f"Failed to set shading style {path} to {value}")
def set_query(self, context):
self.include_global_ids = []
@@ -1732,7 +1887,7 @@ class RemoveSchedule(bpy.types.Operator, Operator):
schedule: bpy.props.IntProperty()
def _execute(self, context):
core.remove_document(tool.Ifc, tool.Drawing, "SCHEDULE", schedule=tool.Ifc.get().by_id(self.schedule))
core.remove_document(tool.Ifc, tool.Drawing, "SCHEDULE", document=tool.Ifc.get().by_id(self.schedule))
class OpenSchedule(bpy.types.Operator, Operator):
@@ -2273,6 +2428,34 @@ class DisableEditingDrawings(bpy.types.Operator, Operator):
core.disable_editing_drawings(tool.Drawing)
class ExpandTargetView(bpy.types.Operator):
bl_idname = "bim.expand_target_view"
bl_label = "Expand Target View"
bl_options = {"REGISTER", "UNDO"}
target_view: bpy.props.StringProperty()
def execute(self, context):
props = context.scene.DocProperties
for drawing in [d for d in props.drawings if d.target_view == self.target_view]:
drawing.is_expanded = True
core.load_drawings(tool.Drawing)
return {"FINISHED"}
class ContractTargetView(bpy.types.Operator):
bl_idname = "bim.contract_target_view"
bl_label = "Contract Target View"
bl_options = {"REGISTER", "UNDO"}
target_view: bpy.props.StringProperty()
def execute(self, context):
props = context.scene.DocProperties
for drawing in [d for d in props.drawings if d.target_view == self.target_view]:
drawing.is_expanded = False
core.load_drawings(tool.Drawing)
return {"FINISHED"}
class ExpandSheet(bpy.types.Operator):
bl_idname = "bim.expand_sheet"
bl_label = "Expand Sheet"
@@ -165,8 +165,9 @@ def get_diagram_scales(self, context):
def update_drawing_name(self, context):
drawing = tool.Ifc.get().by_id(self.ifc_definition_id)
core.update_drawing_name(tool.Ifc, tool.Drawing, drawing=drawing, name=self.name)
if self.ifc_definition_id:
drawing = tool.Ifc.get().by_id(self.ifc_definition_id)
core.update_drawing_name(tool.Ifc, tool.Drawing, drawing=drawing, name=self.name)
def get_drawing_style_name(self):
@@ -253,6 +254,8 @@ class Drawing(PropertyGroup):
name: StringProperty(name="Name", update=update_drawing_name)
target_view: StringProperty(name="Target View")
is_selected: BoolProperty(name="Is Selected", default=True)
is_drawing: BoolProperty(name="Is Drawing", default=False)
is_expanded: BoolProperty(name="Is Expanded", default=True)
class Document(PropertyGroup):
@@ -297,6 +300,7 @@ class RasterStyleProperty(enum.Enum):
SHADING = "scene.display.shading"
DISPLAY = "scene.display"
OVERLAY = "space.overlay"
SPACE_SHADING = "space.shading"
RASTER_STYLE_PROPERTIES_EXCLUDE = (
@@ -495,11 +499,10 @@ ANNOTATION_TYPES_DATA = {
"TEXT": ("Text", "", "SMALL_CAPS", "empty"),
"TEXT_LEADER": ("Leader", "", "TRACKING_BACKWARDS", "curve"),
"STAIR_ARROW": ("Stair Arrow", "Add stair arrow annotation.\nIf you have IfcStairFlight object selected, it will be used as a reference for the annotation", "SCREEN_BACK", "curve"),
"HIDDEN_LINE": ("Hidden", "", "CON_TRACKTO", "mesh"),
"PLAN_LEVEL": ("Level (Plan)", "", "SORTBYEXT", "curve"),
"SECTION_LEVEL": ("Level (Section)", "", "TRIA_DOWN", "curve"),
"BREAKLINE": ("Breakline", "", "FCURVE", "mesh"),
"LINEWORK": ("Line", "", "MESH_MONKEY", "mesh"),
"LINEWORK": ("Line", "", "SNAP_MIDPOINT", "mesh"),
"BATTING": ("Batting", "Add batting annotation.\nThickness could be changed through Thickness property of BBIM_Batting property set", "FORCE_FORCE", "mesh"),
"REVISION_CLOUD":("Revision Cloud", "Add revision cloud", "VOLUME_DATA", "mesh"),
"FILL_AREA": ("Fill Area", "", "NODE_TEXTURE", "mesh"),
@@ -510,13 +513,10 @@ ANNOTATION_TYPES_DATA = {
annotation_classes = [(x, *ANNOTATION_TYPES_DATA[x][:3], i) for i, x in enumerate(ANNOTATION_TYPES_DATA)]
def get_annotation_data_prop(prop_name):
def function(self, context):
if not AnnotationData.is_loaded:
AnnotationData.load()
return AnnotationData.data[prop_name]
return function
def get_relating_type_id(self, context):
if not AnnotationData.is_loaded:
AnnotationData.load()
return AnnotationData.data["relating_type_id"]
def update_annotation_object_type(self, context):
@@ -533,12 +533,12 @@ class BIMAnnotationProperties(PropertyGroup):
object_type: bpy.props.EnumProperty(
name="Annotation Object Type", items=annotation_classes, default="TEXT", update=update_annotation_object_type
)
relating_type_id: bpy.props.EnumProperty(
name="Relating Annotation Type", items=get_annotation_data_prop("relating_types")
)
relating_type_id: bpy.props.EnumProperty(name="Relating Annotation Type", items=get_relating_type_id)
create_representation_for_type: bpy.props.BoolProperty(
name="Create Representation For Type",
default=False,
description='Whether "Add type" should define a representation for the type \n'
"or allow occurences to have their own",
)
is_adding_type: bpy.props.BoolProperty(default=False)
type_name: bpy.props.StringProperty(name="Name", default="TYPEX")
@@ -351,7 +351,7 @@ class Scheduler:
wrap_text: if True, text will be wrapped to fit in cell
cell_width: width of cell, used for wrapping text
"""
text_lines = [str(p).upper() for p in p_tags]
text_lines = [str(p) for p in p_tags]
box_alignment_params = SvgWriter.get_box_alignment_parameters(box_alignment)
text_params = {
"font-size": font_size,
@@ -310,10 +310,13 @@ class SheetBuilder:
brackets_level -= 1
text += l
# replace urls url(#marker) with url(#prefix-marker)
# since url(#marker.prefix) doesn't seem to work
text = re.sub(r"url\(#([^\)]+)\)", rf"url(#{prefix}-\1)", text)
style.text = text
def replace_urls(text):
"""replace urls `url(#marker)` with `url(#prefix-marker)`
since `url(#marker.prefix)` doesn't seem to work
"""
return re.sub(r"url\(#([^\)]+)\)", rf"url(#{prefix}-\1)", text)
style.text = replace_urls(text)
for svg_element in svg.findall(f".//*"):
if svg_element.tag in (f"{SVG}style", f"{SVG}svg"):
@@ -325,6 +328,9 @@ class SheetBuilder:
# add class "prefix" to all classes
if "class" in attrib:
attrib["class"] += f" {prefix}"
if "filter" in attrib:
# example use "#fill-background" filter
attrib["filter"] = replace_urls(attrib["filter"])
if svg_element.tag == f"{SVG}use":
href_attrib = f"{XLINK}href"
if href_attrib in attrib:
@@ -100,6 +100,9 @@ class SvgWriter:
if not os.path.exists(resource_path):
resource_basename = os.path.basename(resource_path)
ootb_resource = os.path.join(bpy.context.scene.BIMProperties.data_dir, "assets", resource_basename)
print(
f"WARNING. Couldn't find {resource} for the drawing by the path: {resource_path}. Default BBIM resource will be copied from {ootb_resource}"
)
if os.path.exists(ootb_resource):
shutil.copy(ootb_resource, resource_path)
self.resource_paths[resource] = resource_path
@@ -121,23 +124,35 @@ class SvgWriter:
self.height = self.raw_height * self.svg_scale
def add_stylesheet(self):
if not self.resource_paths["Stylesheet"] or not os.path.exists(self.resource_paths["Stylesheet"]):
path = self.resource_paths["Stylesheet"]
if not path:
return
with open(self.resource_paths["Stylesheet"], "r") as stylesheet:
if not os.path.exists(path):
print(f"WARNING. Couldn't find stylesheet for the drawing by the path: {path}")
return
with open(path, "r") as stylesheet:
self.svg.defs.add(self.svg.style(stylesheet.read()))
def add_markers(self):
if not self.resource_paths["Markers"] or not os.path.exists(self.resource_paths["Markers"]):
path = self.resource_paths["Markers"]
if not path:
return
tree = ET.parse(self.resource_paths["Markers"])
if not os.path.exists(path):
print(f"WARNING. Couldn't find markers for the drawing by the path: {path}")
return
tree = ET.parse(path)
root = tree.getroot()
for child in root:
self.svg.defs.add(External(child))
def add_symbols(self):
if not self.resource_paths["Symbols"] or not os.path.exists(self.resource_paths["Symbols"]):
path = self.resource_paths["Symbols"]
if not path:
return
tree = ET.parse(self.resource_paths["Symbols"])
if not os.path.exists(path):
print(f"WARNING. Couldn't find symbols for the drawing by the path: {path}")
return
tree = ET.parse(path)
root = tree.getroot()
for child in root:
self.svg.defs.add(External(child))
@@ -148,9 +163,15 @@ class SvgWriter:
return External(xml_symbol) if xml_symbol else None
def add_patterns(self):
if not self.resource_paths["Patterns"] or not os.path.exists(self.resource_paths["Patterns"]):
path = self.resource_paths["Patterns"]
if not path:
return
tree = ET.parse(self.resource_paths["Patterns"])
if not os.path.exists(path):
print(f"WARNING. Couldn't find patterns for the drawing by the path: {path}")
return
if not path or not os.path.exists(path):
return
tree = ET.parse(path)
root = tree.getroot()
for child in root:
self.svg.defs.add(External(child))
@@ -182,8 +203,6 @@ class SvgWriter:
self.draw_section_annotation(obj)
elif element.ObjectType == "BREAKLINE":
self.draw_break_annotations(obj)
elif element.ObjectType == "HIDDEN_LINE":
self.draw_line_annotation(obj)
elif element.ObjectType == "PLAN_LEVEL":
self.draw_plan_level_annotation(obj)
elif element.ObjectType == "SECTION_LEVEL":
@@ -200,51 +219,42 @@ class SvgWriter:
return self
def draw_section_level_annotation(self, obj):
x_offset = self.raw_width / 2
y_offset = self.raw_height / 2
offset = Vector([self.raw_width, self.raw_height]) / 2
matrix_world = obj.matrix_world
classes = self.get_attribute_classes(obj)
element = tool.Ifc.get_entity(obj)
storey = tool.Drawing.get_annotation_element(element)
tag = storey.Name if storey else element.Description
dimension_data = DecoratorData.get_dimension_data(obj)
suppress_zero_inches = dimension_data["suppress_zero_inches"]
base_offset_y = 3.5
for spline in obj.data.splines:
points = self.get_spline_points(spline)
projected_points = [self.project_point_onto_camera(matrix_world @ p.co.xyz) for p in points]
d = " ".join(
[
"L {} {}".format((x_offset + p.x) * self.svg_scale, (y_offset - p.y) * self.svg_scale)
for p in projected_points
]
)
projected_points_svg = [(offset + p.xy * Vector((1, -1))) * self.svg_scale for p in projected_points]
d = " ".join(["L {} {}".format(*p) for p in projected_points_svg])
d = "M{}".format(d[1:])
path = self.svg.add(self.svg.path(d=d, class_=" ".join(classes)))
text_position = Vector(
(
(x_offset + projected_points[0].x) * self.svg_scale,
((y_offset - projected_points[0].y) * self.svg_scale) - 3.5,
)
)
text_position = projected_points_svg[0] - Vector((0, base_offset_y))
vector = projected_points_svg[0] - projected_points_svg[1]
angle = math.degrees(vector.angle_signed(Vector((1, 0))))
# TODO: allow metric to be configurable
rl_value = (matrix_world @ points[0].co.xyz).z
if bpy.context.scene.unit_settings.system == "IMPERIAL":
rl = helper.format_distance(rl_value, precision=self.precision, decimal_places=self.decimal_places)
else:
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
rl = rl_value / unit_scale
rl = ifcopenshell.util.geolocation.auto_z2e(tool.Ifc.get(), rl)
rl *= unit_scale
rl = "{:.3f}m".format(rl)
text_style = SvgWriter.get_box_alignment_parameters("bottom-left")
self.svg.add(
self.svg.text(
"RL {}{}".format("" if rl_value < 0 else "+", rl),
insert=tuple(text_position),
class_="SECTIONLEVEL",
**text_style,
def get_text():
z = (matrix_world @ points[0].co.xyz).z
rl = helper.format_distance(
z,
precision=self.precision,
decimal_places=self.decimal_places,
suppress_zero_inches=suppress_zero_inches,
)
text = "RL {}{}".format("" if z < 0 else "+", rl)
return text
self.draw_dimension_text(
get_text, tag, dimension_data, text_position=text_position, angle=angle, class_str="SECTIONLEVEL"
)
if tag:
self.svg.add(self.svg.text(tag, insert=(text_position[0], text_position[1] - 5), **text_style))
def draw_stair_annotation(self, obj):
x_offset = self.raw_width / 2
@@ -320,9 +330,10 @@ class SvgWriter:
return
classes = self.get_attribute_classes(obj)
if len(obj.data.polygons) == 0:
self.draw_edge_annotation(obj, classes)
return
if len(obj.data.vertices) and not len(obj.data.edges):
return self.draw_point_annotation(obj, classes)
elif len(obj.data.polygons) == 0:
return self.draw_edge_annotation(obj, classes)
bm = bmesh.new()
bm.from_mesh(obj.data)
@@ -489,9 +500,9 @@ class SvgWriter:
return pattern
def get_scale(size, direction):
vector = direction * size
shrinked_vector = size // segment_width * segment_width * direction
scale = [1 if vector[i] == 0 else vector[i] / shrinked_vector[i] for i in range(2)]
original_edge = direction * size
current_svg_segments = ceil(size / segment_width) * segment_width * direction
scale = [1 if original_edge[i] == 0 else original_edge[i] / current_svg_segments[i] for i in range(2)]
return "scale(%f, %f)" % (scale[0], scale[1])
def poly_to_edges(poly):
@@ -545,11 +556,16 @@ class SvgWriter:
pattern_dir = pattern_edge.normalized()
pattern_length = pattern_edge.length
segments = int(pattern_length // segment_width)
segments = ceil(pattern_length / segment_width)
pattern_dir_step = pattern_dir * segment_width
points = [pattern_dir_step * i for i in range(segments)]
# it takes atleast 2 points to preserve the edge direction
# if there is just 1 segment then we still add second point and then hide the "marker-end"
n_points = max(segments, 2)
points = [pattern_dir_step * i for i in range(n_points)]
polyline_style = f"marker: url(#{marker_id}); stroke: none;"
polyline_style = f"marker: url(#{marker_id}); stroke: none; "
if segments == 1:
polyline_style += "marker-end: none; "
polyline_transform = f"translate({start_svg.x}, {start_svg.y}) {get_scale(pattern_length, pattern_dir)}"
polyline = self.svg.polyline(
points=points, class_=" ".join(classes), style=polyline_style, transform=polyline_transform
@@ -705,15 +721,19 @@ class SvgWriter:
drawing = tool.Drawing.get_annotation_element(element)
reference = tool.Drawing.get_drawing_reference(drawing)
if reference:
sheet = tool.Drawing.get_reference_document(reference)
if sheet:
if tool.Ifc.get_schema() == "IFC2X3":
reference_id = reference.ItemReference or "-"
sheet_id = sheet.DocumentId or "-"
else:
reference_id = reference.Identification or "-"
sheet_id = sheet.Identification or "-"
return (reference_id, sheet_id)
for sheet_reference in tool.Ifc.get().by_type("IfcDocumentReference"):
if sheet_reference.Description != "DRAWING" or sheet_reference.Location != reference.Location:
continue
sheet = tool.Drawing.get_reference_document(sheet_reference)
if sheet:
if tool.Ifc.get_schema() == "IFC2X3":
reference_id = sheet_reference.ItemReference or "-"
sheet_id = sheet.DocumentId or "-"
else:
reference_id = sheet_reference.Identification or "-"
sheet_id = sheet.Identification or "-"
return (reference_id, sheet_id)
break
return ("-", "-")
@staticmethod
@@ -771,7 +791,14 @@ class SvgWriter:
return matrix.inverted()[i].to_3d().normalized()
text_dir_world_x_axis = get_basis_vector(text_obj.matrix_world)
text_dir = (self.camera.matrix_world.inverted().to_quaternion() @ text_dir_world_x_axis).to_2d().normalized()
# RCP cameras may be scaled, so reset scales.
camera_matrix = self.camera.matrix_world.copy()
camera_matrix[0][0] = 1
camera_matrix[1][1] = 1
camera_matrix[2][2] = 1
text_dir = (camera_matrix.inverted().to_quaternion() @ text_dir_world_x_axis).to_2d().normalized()
angle = math.degrees(-text_dir.angle_signed(Vector((1, 0))))
classes = self.get_attribute_classes(text_obj)
@@ -807,45 +834,39 @@ class SvgWriter:
line_number = 0
for text_literal in text_literals:
# after pretty indentation some redundant spaces can occur in svg tags
# this is why we apply "font-size: 0;" to the text tag to remove those spaces
# and add clases to the tspan tags
# ref: https://github.com/IfcOpenShell/IfcOpenShell/issues/2833#issuecomment-1471584960
text = tool.Drawing.replace_text_literal_variables(text_literal.Literal, product)
attribs = {
"transform": text_transform,
"style": "font-size: 0;",
}
text_tags = self.create_text_tag(
text,
text_position_svg,
angle,
text_literal.BoxAlignment,
classes_str,
fill_bg="fill-bg" in classes,
line_number_start=line_number,
)
for tag in text_tags:
self.svg.add(tag)
line_number += len(tag.elements)
def add_text_tag(add_fill_bg):
nonlocal line_number
text_tag = self.svg.text(
"",
**(attribs | {"filter": "url(#fill-background)"}) if add_fill_bg else attribs,
**SvgWriter.get_box_alignment_parameters(text_literal.BoxAlignment),
)
self.svg.add(text_tag)
def draw_point_annotation(self, obj, classes):
x_offset = self.raw_width / 2
y_offset = self.raw_height / 2
text_lines = text.replace("\\n", "\n").split("\n")
matrix_world = obj.matrix_world
projected_points = [self.project_point_onto_camera(matrix_world @ v.co) for v in obj.data.vertices]
for text_line in text_lines:
# position has to be inserted at tspan to avoid x offset between tspans
# note that tspan doesn't support using `transform` attribute
# so we use (0,0) position because tspan is already offseted by text transform
tspan = self.svg.tspan(text_line, class_=classes_str, insert=(0, 0))
# doing it here and not in tspan constructor because constructor adds unnecessary spaces
tspan.update({"dy": f"{line_number}em"})
text_tag.add(tspan)
line_number += 1
element = tool.Ifc.get_entity(obj)
svg_id = str(ifcopenshell.util.element.get_predefined_type(element))
if add_fill_bg:
# return line_number back to the original value
line_number -= len(text_lines)
# EPset_AnnotationSurveyArea is not standard! See bSI-4.3 proposal #660.
point_type = ifcopenshell.util.element.get_pset(element, "EPset_AnnotationSurveyArea", "PointType")
if point_type:
svg_id += f"-{point_type}"
if "fill-bg" in classes:
add_text_tag(True)
add_text_tag(False)
for symbol_position in projected_points:
symbol_position = Vector(((x_offset + symbol_position.x), (y_offset - symbol_position.y)))
symbol_position_svg = symbol_position * self.svg_scale
self.svg.add(self.svg.use(f"#{svg_id}", insert=symbol_position_svg))
def draw_break_annotations(self, obj):
x_offset = self.raw_width / 2
@@ -871,53 +892,60 @@ class SvgWriter:
path = self.svg.add(self.svg.path(d=d, class_=" ".join(classes)))
def draw_plan_level_annotation(self, obj):
x_offset = self.raw_width / 2
y_offset = self.raw_height / 2
offset = Vector([self.raw_width, self.raw_height]) / 2
matrix_world = obj.matrix_world
classes = self.get_attribute_classes(obj)
element = tool.Ifc.get_entity(obj)
description = element.Description
dimension_data = DecoratorData.get_dimension_data(obj)
suppress_zero_inches = dimension_data["suppress_zero_inches"]
base_offset_y = 1.0
for spline in obj.data.splines:
points = self.get_spline_points(spline)
projected_points = [self.project_point_onto_camera(matrix_world @ p.co.xyz) for p in points]
d = " ".join(
[
"L {} {}".format((x_offset + p.x) * self.svg_scale, (y_offset - p.y) * self.svg_scale)
for p in projected_points
]
)
projected_points_svg = [(offset + p.xy * Vector((1, -1))) * self.svg_scale for p in projected_points]
d = " ".join(["L {} {}".format(*p) for p in projected_points_svg])
d = "M{}".format(d[1:])
path = self.svg.add(self.svg.path(d=d, class_=" ".join(classes)))
text_position = Vector(
(
(x_offset + projected_points[0].x) * self.svg_scale,
((y_offset - projected_points[0].y) * self.svg_scale) - 1.0,
)
)
# TODO: allow metric to be configurable
rl_value = (matrix_world @ points[0].co).z
if bpy.context.scene.unit_settings.system == "IMPERIAL":
rl = helper.format_distance(rl_value, precision=self.precision, decimal_places=self.decimal_places)
text_position = projected_points_svg[0] - Vector((0, base_offset_y))
text_dir = projected_points_svg[1] - projected_points_svg[0]
if text_dir.x < 0:
box_alignment = "bottom-right"
text_dir *= -1
else:
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
rl = rl_value / unit_scale
rl = ifcopenshell.util.geolocation.auto_z2e(tool.Ifc.get(), rl)
rl *= unit_scale
rl = "{:.3f}m".format(rl)
box_alignment = "bottom-left"
angle = math.degrees(text_dir.angle_signed(Vector((1, 0))))
box_alignment = "bottom-left" if projected_points[0].x <= projected_points[-1].x else "bottom-right"
text_style = SvgWriter.get_box_alignment_parameters(box_alignment)
self.svg.add(
self.svg.text(
"{}{}".format("" if rl_value < 0 else "+", rl),
insert=tuple(text_position),
class_="PLANLEVEL",
**text_style,
# TODO: allow metric to be configurable
def get_text():
z = (matrix_world @ points[0].co.xyz).z
rl = helper.format_distance(
z,
precision=self.precision,
decimal_places=self.decimal_places,
suppress_zero_inches=suppress_zero_inches,
)
text = "{}{}".format("" if z < 0 else "+", rl)
return text
self.draw_dimension_text(
get_text,
description,
dimension_data,
text_position=text_position,
angle=angle,
class_str="PLANLEVEL",
box_alignment=box_alignment,
)
def draw_angle_annotations(self, obj):
points = obj.data.splines[0].points
region = bpy.context.region
region_3d = bpy.context.area.spaces.active.region_3d
area = tool.Blender.get_viewport_context()["area"]
region_3d = area.spaces.active.region_3d
points_chunked = [points[i : i + 3] for i in range(len(points) - 2)]
for points_chunk in points_chunked:
@@ -938,7 +966,7 @@ class SvgWriter:
p0 = points_2d[1] + dir0 * angle_radius
p2 = points_2d[1] + dir1 * angle_radius
points_chunk = [view3d_utils.region_2d_to_origin_3d(region, region_3d, p) for p in [p0, p3, p2]]
# points = [p.co.xyz for p in bpy.context.object.data.splines[0].points[:3]]
# points = [p.co.xyz for p in bpy.context.active_object.data.splines[0].points[:3]]
bm = bmesh.new()
bm.verts.index_update()
@@ -1051,48 +1079,61 @@ class SvgWriter:
path = self.svg.add(self.svg.path(d=d, class_=" ".join(classes)))
def draw_radius_annotations(self, obj):
x_offset = self.raw_width / 2
y_offset = self.raw_height / 2
offset = Vector([self.raw_width, self.raw_height]) / 2
classes = self.get_attribute_classes(obj)
element = tool.Ifc.get_entity(obj)
tag = element.Description
matrix_world = obj.matrix_world
dimension_data = DecoratorData.get_dimension_data(obj)
for spline in obj.data.splines:
points = self.get_spline_points(spline)
projected_points = [self.project_point_onto_camera(matrix_world @ p.co.xyz) for p in points]
d = " ".join(
[
"L {} {}".format((x_offset + p.x) * self.svg_scale, (y_offset - p.y) * self.svg_scale)
for p in projected_points
]
)
projected_points_svg = [(offset + p.xy * Vector((1, -1))) * self.svg_scale for p in projected_points]
d = " ".join(["L {} {}".format(*p) for p in projected_points_svg])
d = "M{}".format(d[1:])
path = self.svg.add(self.svg.path(d=d, class_=" ".join(classes)))
p0 = projected_points_svg[0]
p1 = projected_points_svg[1]
p0 = Vector(
(
(x_offset + projected_points[0].x) * self.svg_scale,
(y_offset - projected_points[0].y) * self.svg_scale,
)
)
p1 = Vector(
(
(x_offset + projected_points[1].x) * self.svg_scale,
(y_offset - projected_points[1].y) * self.svg_scale,
)
)
text_offset = (p0 - p1).xy.normalized() * 5
text_position = projected_points[0]
text_position = Vector(
((x_offset + text_position.x) * self.svg_scale, (y_offset - text_position.y) * self.svg_scale)
)
text_position += text_offset
text_offset = (p0 - p1).normalized() * 5
text_position = p0 + text_offset
text_style = SvgWriter.get_box_alignment_parameters("center")
radius = (points[-1].co - points[-2].co).length
radius = helper.format_distance(radius, precision=self.precision, decimal_places=self.decimal_places)
tag = element.Description or f"R{radius}"
def get_text():
radius = (points[-1].co - points[-2].co).length
radius = helper.format_distance(radius, precision=self.precision, decimal_places=self.decimal_places)
text = f"R{radius}"
return text
self.svg.add(self.svg.text(tag, insert=tuple(text_position), class_="RADIUS", **text_style))
self.draw_dimension_text(
get_text, tag, dimension_data, text_position=text_position, class_str="RADIUS", box_alignment="center"
)
def draw_dimension_text(self, get_text, tag, dimension_data, **create_text_kwargs):
prefix = dimension_data["text_prefix"]
suffix = dimension_data["text_suffix"]
show_description_only = dimension_data["show_description_only"]
fill_bg = dimension_data["fill_bg"]
text_tags = []
line_number_start = 0
if not show_description_only:
text = get_text()
full_prefix = ((tag + "\\n") if tag else "") + prefix
text = full_prefix + text + suffix
line_number_start -= full_prefix.count("\\n")
else:
if not tag:
return
text = tag
text_tags += self.create_text_tag(
text, line_number_start=line_number_start, fill_bg=fill_bg, **create_text_kwargs
)
for text in text_tags:
self.svg.add(text)
def draw_fall_annotations(self, obj):
x_offset = self.raw_width / 2
@@ -1187,6 +1228,7 @@ class SvgWriter:
suppress_zero_inches=dimension_data["suppress_zero_inches"],
text_prefix=dimension_data["text_prefix"],
text_suffix=dimension_data["text_suffix"],
fill_bg=dimension_data["fill_bg"],
)
def draw_dimension_annotations(self, obj):
@@ -1210,6 +1252,7 @@ class SvgWriter:
suppress_zero_inches=dimension_data["suppress_zero_inches"],
text_prefix=dimension_data["text_prefix"],
text_suffix=dimension_data["text_suffix"],
fill_bg=dimension_data["fill_bg"],
)
def draw_measureit_arch_dimension_annotations(self):
@@ -1235,6 +1278,7 @@ class SvgWriter:
suppress_zero_inches=False,
text_prefix="",
text_suffix="",
fill_bg=False,
):
offset = Vector([self.raw_width, self.raw_height]) / 2
v0 = self.project_point_onto_camera(v0_global)
@@ -1244,13 +1288,6 @@ class SvgWriter:
mid = ((end - start) / 2) + start
vector = end - start
perpendicular = Vector((vector.y, -vector.x)).normalized()
dimension = (v1_global - v0_global).length
dimension = helper.format_distance(
dimension,
precision=self.precision,
decimal_places=self.decimal_places,
suppress_zero_inches=suppress_zero_inches,
)
sheet_dimension = (end - start).length
# if annotation can't fit offset text to the right of marker
@@ -1260,81 +1297,109 @@ class SvgWriter:
line = self.svg.line(start=start, end=end, class_=" ".join(classes))
self.svg.add(line)
if not show_description_only:
text = f"{text_prefix}{str(dimension)}{text_suffix}"
text_tag = self.create_text_tag(
text,
text_position + perpendicular,
angle,
"bottom-middle",
"DIMENSION",
text_format=text_format,
multiline=True,
multiline_to_bottom=False,
)
self.svg.add(text_tag)
if dimension_text:
text_tag = self.create_text_tag(
dimension_text,
text_position - perpendicular,
angle,
"top-middle",
"DIMENSION",
text_format=text_format,
multiline=True,
multiline_to_bottom=True,
)
self.svg.add(text_tag)
text_tags = []
text_tag_kwargs = {
"angle": angle,
"class_str": "DIMENSION",
"text_format": text_format,
"fill_bg": fill_bg,
}
elif show_description_only and dimension_text:
text_tag = self.create_text_tag(
dimension_text,
text_position + perpendicular,
angle,
"bottom-middle",
"DIMENSION",
text_format=text_format,
multiline=True,
multiline_to_bottom=False,
if not show_description_only:
dimension = (v1_global - v0_global).length
dimension = helper.format_distance(
dimension,
precision=self.precision,
decimal_places=self.decimal_places,
suppress_zero_inches=suppress_zero_inches,
)
self.svg.add(text_tag)
text = text_prefix + str(dimension) + text_suffix
else:
if not dimension_text:
return
text = dimension_text
text_tags += self.create_text_tag(
text,
text_position + perpendicular,
box_alignment="bottom-middle",
multiline_to_bottom=False,
**text_tag_kwargs,
)
if not show_description_only and dimension_text:
text_tags += self.create_text_tag(
dimension_text,
text_position - perpendicular,
box_alignment="top-middle",
multiline_to_bottom=True,
**text_tag_kwargs,
)
for tag in text_tags:
self.svg.add(tag)
def create_text_tag(
self,
text,
text_position,
angle,
box_alignment,
class_str,
angle=0.0,
box_alignment="bottom-left",
class_str="",
text_format=lambda x: x,
multiline=False,
multiline_to_bottom=False,
multiline=True,
multiline_to_bottom=True,
fill_bg=False,
line_number_start=0,
_draw_fill_bg=False,
):
"""returns list of created text tags"""
text_tags = []
if fill_bg:
method_kwargs = locals() | {"_draw_fill_bg": True, "fill_bg": False}
del method_kwargs["self"]
del method_kwargs["text_tags"]
text_tags += self.create_text_tag(**method_kwargs)
base_text_attrs = SvgWriter.get_box_alignment_parameters(box_alignment)
base_text_attrs = base_text_attrs | ({"filter": "url(#fill-background)"} if _draw_fill_bg else {})
if not multiline:
text_kwargs = {"transform": "rotate({} {} {})".format(angle, text_position.x, text_position.y)}
return self.svg.text(
transform_kwargs = {"transform": "rotate({} {} {})".format(angle, text_position.x, text_position.y)}
text_tag = self.svg.text(
text_format(text),
insert=text_position,
class_=class_str,
**(text_kwargs | SvgWriter.get_box_alignment_parameters(box_alignment)),
**(transform_kwargs | base_text_attrs),
)
text_tags.append(text_tag)
return text_tags
text_position_svg_str = ", ".join(map(str, text_position))
text_transform = f"translate({text_position_svg_str}) rotate({angle})"
# after pretty indentation some redundant spaces can occur in svg tags
# this is why we apply "font-size: 0;" to the text tag to remove those spaces
# and add clases to the tspan tags
# ref: https://github.com/IfcOpenShell/IfcOpenShell/issues/2833#issuecomment-1471584960
text_kwargs = {
"transform": text_transform,
"style": "font-size: 0;",
}
text_tag = self.svg.text("", **text_kwargs, **SvgWriter.get_box_alignment_parameters(box_alignment))
text_tag = self.svg.text("", **text_kwargs, **base_text_attrs)
text_tags.append(text_tag)
text_lines = text.replace("\\n", "\n").split("\n")
text_lines = text_lines if multiline_to_bottom else text_lines[::-1]
for line_number, text_line in enumerate(text_lines):
for line_number, text_line in enumerate(text_lines, line_number_start):
# position has to be inserted at tspan to avoid x offset between tspans
# note that tspan doesn't support using `transform` attribute
# so we use (0,0) position because tspan is already offseted by text transform
tspan = self.svg.tspan(text_format(text_line), class_=class_str, insert=(0, 0))
# doing it here and not in tspan constructor because constructor adds unnecessary spaces
tspan.update({"dy": f"{line_number if multiline_to_bottom else -line_number}em"})
text_tag.add(tspan)
return text_tag
return text_tags
def project_point_onto_camera(self, point):
# TODO is this needlessly complex?
@@ -161,13 +161,13 @@ class BIM_PT_drawing_underlay(Panel):
class BIM_PT_drawings(Panel):
bl_label = "Drawings"
bl_idname = "BIM_PT_drawings"
bl_space_type = "VIEW_3D"
bl_region_type = "UI"
bl_category = "BIM Documentation"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
@classmethod
def poll(cls, context):
return tool.Ifc.get()
return tool.Blender.is_tab(context, "DRAWINGS") and tool.Ifc.get()
def draw(self, context):
if not DrawingsData.is_loaded:
@@ -199,18 +199,31 @@ class BIM_PT_drawings(Panel):
col.alignment = "LEFT"
row2 = col.row(align=True)
row2.operator("bim.remove_drawing", icon="X", text="").drawing = active_drawing.ifc_definition_id
row2.operator(
"bim.duplicate_drawing", icon="COPYDOWN", text=""
).drawing = active_drawing.ifc_definition_id
col = row.column()
col.alignment = "RIGHT"
op = row.operator("bim.select_all_drawings", icon="SELECT_SUBTRACT", text="")
op = row.operator("bim.open_drawing", icon="URL", text="")
open_drawing_button = row.row(align=True)
op = open_drawing_button.operator("bim.open_drawing", icon="URL", text="")
op.view = active_drawing.name
open_drawing_button.enabled = active_drawing.ifc_definition_id > 0
row.operator("bim.activate_model", icon="VIEW3D", text="")
op = row.operator("bim.activate_drawing", icon="OUTLINER_OB_CAMERA", text="")
drawing_button = row.row(align=True)
op = drawing_button.operator("bim.activate_drawing", icon="OUTLINER_OB_CAMERA", text="")
op.drawing = active_drawing.ifc_definition_id
row.operator("bim.create_drawing", text="", icon="OUTPUT")
drawing_button.enabled = active_drawing.ifc_definition_id > 0
create_drawing_button = row.row(align=True)
create_drawing_button.operator("bim.create_drawing", text="", icon="OUTPUT")
create_drawing_button.enabled = active_drawing.ifc_definition_id > 0
self.layout.template_list(
"BIM_UL_drawinglist", "", self.props, "drawings", self.props, "active_drawing_index"
)
@@ -229,13 +242,13 @@ class BIM_PT_drawings(Panel):
class BIM_PT_schedules(Panel):
bl_label = "Schedules"
bl_idname = "BIM_PT_schedules"
bl_space_type = "VIEW_3D"
bl_region_type = "UI"
bl_category = "BIM Documentation"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
@classmethod
def poll(cls, context):
return tool.Ifc.get()
return tool.Blender.is_tab(context, "DRAWINGS") and tool.Ifc.get()
def draw(self, context):
if not DocumentsData.is_loaded:
@@ -276,21 +289,18 @@ class BIM_PT_schedules(Panel):
def draw_project_not_saved_ui(self):
row = self.layout.row()
row.label(text="Project Not Yet Saved", icon="ERROR")
row = self.layout.row()
op = row.operator("export_ifc.bim", icon="EXPORT", text="Save Project")
op.should_save_as = False
class BIM_PT_references(Panel):
bl_label = "References"
bl_idname = "BIM_PT_references"
bl_space_type = "VIEW_3D"
bl_region_type = "UI"
bl_category = "BIM Documentation"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
@classmethod
def poll(cls, context):
return tool.Ifc.get()
return tool.Blender.is_tab(context, "DRAWINGS") and tool.Ifc.get()
def draw(self, context):
if not DocumentsData.is_loaded:
@@ -304,7 +314,7 @@ class BIM_PT_references(Panel):
if not self.props.is_editing_references:
row = self.layout.row(align=True)
row.label(text=f"{DocumentsData.data['total_references']} References Found", icon="LONGDISPLAY")
row.label(text=f"{DocumentsData.data['total_references']} References Found", icon="OBJECT_HIDDEN")
row.operator("bim.load_references", text="", icon="IMPORT")
return
@@ -328,13 +338,13 @@ class BIM_PT_references(Panel):
class BIM_PT_sheets(Panel):
bl_label = "Sheets"
bl_idname = "BIM_PT_sheets"
bl_space_type = "VIEW_3D"
bl_region_type = "UI"
bl_category = "BIM Documentation"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
@classmethod
def poll(cls, context):
return tool.Ifc.get()
return tool.Blender.is_tab(context, "DRAWINGS") and tool.Ifc.get()
def draw(self, context):
if not SheetsData.is_loaded:
@@ -377,12 +387,13 @@ class BIM_PT_sheets(Panel):
class BIM_PT_product_assignments(Panel):
bl_label = "IFC Product Assignments"
bl_label = "Product Assignments"
bl_idname = "BIM_PT_product_assignments"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "object"
bl_order = 1
bl_parent_id = "BIM_PT_tab_object_metadata"
@classmethod
def poll(cls, context):
@@ -412,12 +423,13 @@ class BIM_PT_product_assignments(Panel):
class BIM_PT_text(Panel):
bl_label = "IFC Text"
bl_label = "Text"
bl_idname = "BIM_PT_text"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "object"
bl_order = 0
bl_parent_id = "BIM_PT_tab_object_metadata"
@classmethod
def poll(cls, context):
@@ -489,95 +501,22 @@ class BIM_PT_text(Panel):
row.label(text=literal_data[attribute])
class BIM_PT_annotation_utilities(Panel):
bl_idname = "BIM_PT_annotation_utilities"
bl_label = "Annotation"
bl_space_type = "VIEW_3D"
bl_region_type = "UI"
bl_category = "BIM Documentation"
def draw(self, context):
layout = self.layout
self.props = context.scene.DocProperties
row = layout.row(align=True)
op = row.operator("bim.add_annotation", text="Dimension", icon="FIXED_SIZE")
op.object_type = "DIMENSION"
op.data_type = tool.Drawing.get_annotation_data_type(op.object_type)
op = row.operator("bim.add_annotation", text="Angle", icon="DRIVER_ROTATIONAL_DIFFERENCE")
op.object_type = "ANGLE"
op.data_type = tool.Drawing.get_annotation_data_type(op.object_type)
row = layout.row(align=True)
op = row.operator("bim.add_annotation", text="Radius", icon="FORWARD")
op.object_type = "RADIUS"
op.data_type = tool.Drawing.get_annotation_data_type(op.object_type)
op = row.operator("bim.add_annotation", text="Diameter", icon="ARROW_LEFTRIGHT")
op.object_type = "DIAMETER"
op.data_type = tool.Drawing.get_annotation_data_type(op.object_type)
row = layout.row(align=True)
op = row.operator("bim.add_annotation", text="Text", icon="SMALL_CAPS")
op.object_type = "TEXT"
op.data_type = tool.Drawing.get_annotation_data_type(op.object_type)
op = row.operator("bim.add_annotation", text="Leader", icon="TRACKING_BACKWARDS")
op.object_type = "TEXT_LEADER"
op.data_type = tool.Drawing.get_annotation_data_type(op.object_type)
row = layout.row(align=True)
op = row.operator("bim.add_annotation", text="Stair Arrow", icon="SCREEN_BACK")
op.object_type = "STAIR_ARROW"
op.data_type = tool.Drawing.get_annotation_data_type(op.object_type)
op = row.operator("bim.add_annotation", text="Hidden", icon="CON_TRACKTO")
op.object_type = "HIDDEN_LINE"
op.data_type = tool.Drawing.get_annotation_data_type(op.object_type)
row = layout.row(align=True)
op = row.operator("bim.add_annotation", text="Level (Plan)", icon="SORTBYEXT")
op.object_type = "PLAN_LEVEL"
op.data_type = tool.Drawing.get_annotation_data_type(op.object_type)
op = row.operator("bim.add_annotation", text="Level (Section)", icon="TRIA_DOWN")
op.object_type = "SECTION_LEVEL"
op.data_type = tool.Drawing.get_annotation_data_type(op.object_type)
row = layout.row(align=True)
op = row.operator("bim.add_annotation", text="Breakline", icon="FCURVE")
op.object_type = "BREAKLINE"
op.data_type = tool.Drawing.get_annotation_data_type(op.object_type)
op = row.operator("bim.add_annotation", text="Line", icon="MESH_MONKEY")
op.object_type = "LINEWORK"
op.data_type = tool.Drawing.get_annotation_data_type(op.object_type)
row = layout.row(align=True)
op = row.operator("bim.add_annotation", text="Batting", icon="FORCE_FORCE")
op.object_type = "BATTING"
op.data_type = tool.Drawing.get_annotation_data_type(op.object_type)
op.description = "Add batting annotation.\nThickness could be changed through Thickness property of BBIM_Batting property set"
op = row.operator("bim.add_annotation", text="Fill Area", icon="NODE_TEXTURE")
op.object_type = "FILL_AREA"
row = layout.row(align=True)
op = row.operator("bim.add_annotation", text="Fall", icon="SORT_ASC")
op.object_type = "FALL"
op.data_type = tool.Drawing.get_annotation_data_type(op.object_type)
op = row.operator("bim.add_annotation", text="Revision", icon="VOLUME_DATA")
op.object_type = "REVISION_CLOUD"
op.data_type = tool.Drawing.get_annotation_data_type(op.object_type)
row = layout.row(align=True)
row.prop(self.props, "should_draw_decorations", text="Viewport Annotations")
row.enabled = context.scene.camera is not None
class BIM_UL_drawinglist(bpy.types.UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
if item:
row = layout.row(align=True)
if not item:
layout.label(text="", translate=False)
return
row = layout.row(align=True)
if item.is_drawing:
row.label(text="", icon="BLANK1")
selected_icon = "CHECKBOX_HLT" if item.is_selected else "CHECKBOX_DEHLT"
row.prop(item, "is_selected", text="", icon=selected_icon)
icon = "UV_FACESEL"
if item.target_view == "ELEVATION_VIEW":
row.prop(item, "is_selected", text="", icon=selected_icon, emboss=False)
row.prop(item, "name", text="", emboss=False)
else:
if item.target_view == "PLAN_VIEW":
icon = "UV_FACESEL"
elif item.target_view == "ELEVATION_VIEW":
icon = "UV_VERTEXSEL"
elif item.target_view == "SECTION_VIEW":
icon = "UV_EDGESEL"
@@ -585,9 +524,17 @@ class BIM_UL_drawinglist(bpy.types.UIList):
icon = "XRAY"
elif item.target_view == "MODEL_VIEW":
icon = "SNAP_VOLUME"
else:
icon = "CLIPUV_HLT"
if item.is_expanded:
row.operator(
"bim.contract_target_view", text="", emboss=False, icon="DISCLOSURE_TRI_DOWN"
).target_view = item.target_view
else:
row.operator(
"bim.expand_target_view", text="", emboss=False, icon="DISCLOSURE_TRI_RIGHT"
).target_view = item.target_view
row.prop(item, "name", text="", icon=icon, emboss=False)
else:
layout.label(text="", translate=False)
class BIM_UL_sheets(bpy.types.UIList):
@@ -626,3 +573,31 @@ class BIM_UL_sheets(bpy.types.UIList):
else:
name = item.name or "Unnamed"
row.label(text=name)
def draw_filter(self, context, layout):
# We only need filtering, not reordering for sheets.
row = layout.row(align=True)
row.prop(self, "filter_name", text="")
row.prop(self, "use_filter_invert", text="", icon="ARROW_LEFTRIGHT")
def filter_items(self, context, data, propname):
flt_flags = []
flt_neworder = []
if self.filter_name:
filter_name = self.filter_name.lower()
active_sheet = None
for sheet in data.sheets:
if sheet.is_sheet:
active_sheet = sheet
active_sheet_index = len(flt_flags)
if filter_name in sheet.name.lower() or filter_name in sheet.identification.lower():
flt_flags.append(self.bitflag_filter_item)
if not sheet.is_sheet:
flt_flags[active_sheet_index] = self.bitflag_filter_item
else:
flt_flags.append(0)
if not flt_flags:
return flt_flags, flt_neworder
return flt_flags, flt_neworder
@@ -23,14 +23,14 @@ import blenderbim.tool as tool
from blenderbim.bim.helper import prop_with_search
from bpy.types import WorkSpaceTool
# from blenderbim.bim.module.model.data import AuthoringData, RailingData, RoofData
from blenderbim.bim.module.drawing.prop import ANNOTATION_TYPES_DATA
from blenderbim.bim.module.drawing.data import DecoratorData, AnnotationData
from blenderbim.bim.ifc import IfcStore
import blenderbim.bim.handler
# declaring it here to avoid circular import problems
# TODO: Fix circular import.
# Declaring it here to avoid circular import problems
class Operator:
def execute(self, context):
IfcStore.execute_ifc_operator(self, context)
@@ -38,6 +38,77 @@ class Operator:
return {"FINISHED"}
class LaunchAnnotationTypeManager(bpy.types.Operator):
bl_idname = "bim.launch_annotation_type_manager"
bl_label = "Launch Annotation Type Manager"
bl_options = {"REGISTER"}
bl_description = "Manage annotation types and templates"
def execute(self, context):
return {"FINISHED"}
def invoke(self, context, event):
return context.window_manager.invoke_popup(self, width=550)
def draw(self, context):
if not AnnotationData.is_loaded:
AnnotationData.load()
props = context.scene.BIMAnnotationProperties
columns = self.layout.column_flow(columns=3)
row = columns.row()
row.alignment = "LEFT"
row.label(text=f"{len(AnnotationData.data['relating_types'])} Types", icon="FILE_VOLUME")
row = columns.row(align=True)
row.alignment = "CENTER"
# In case you want something here in the future
row = columns.row(align=True)
row.alignment = "RIGHT"
# In case you want something here in the future
if props.is_adding_type:
row = self.layout.row()
box = row.box()
row = box.row()
row.prop(props, "type_name")
row = box.row()
row.prop(props, "create_representation_for_type", text="Geometric Type")
row = box.row(align=True)
row.operator("bim.add_annotation_type", icon="CHECKMARK", text="Save New Type")
row.operator("bim.disable_add_annotation_type", icon="CANCEL", text="")
else:
row = self.layout.row()
row.operator("bim.enable_add_annotation_type", icon="ADD", text="Create New Type")
flow = self.layout.grid_flow(row_major=True, columns=3, even_columns=True, even_rows=True, align=True)
for relating_type in AnnotationData.data["relating_types"]:
outer_col = flow.column()
box = outer_col.box()
row = box.row()
row.alignment = "CENTER"
row.label(text=relating_type["name"], icon="FILE_3D")
row = box.row()
row.alignment = "CENTER"
row.label(text=relating_type["description"])
row = box.row(align=True)
op = row.operator("bim.select_type", icon="OBJECT_DATA")
op.relating_type = relating_type["id"]
op = row.operator("bim.rename_type", icon="GREASEPENCIL", text="")
op.element = relating_type["id"]
op = row.operator("bim.duplicate_type", icon="DUPLICATE", text="")
op.element = relating_type["id"]
op = row.operator("bim.remove_type", icon="X", text="")
op.element = relating_type["id"]
class AnnotationTool(WorkSpaceTool):
bl_space_type = "VIEW_3D"
bl_context_mode = "OBJECT"
@@ -90,41 +161,7 @@ def add_layout_hotkey_operator(layout, text, hotkey, description):
# TODO: move to operator
def create_annotation_type(context):
# just empty to store parameters
props = context.scene.BIMAnnotationProperties
object_type = props.object_type
create_representation = props.create_representation_for_type
drawing = tool.Ifc.get_entity(bpy.context.scene.camera)
if props.create_representation_for_type:
obj = tool.Drawing.create_annotation_object(drawing, object_type)
else:
obj = bpy.data.objects.new(object_type, None)
obj.name = f"{object_type}_TYPE"
obj.location = context.scene.cursor.location
tool.Drawing.ensure_annotation_in_drawing_plane(obj)
drawing = tool.Ifc.get_entity(context.scene.camera)
ifc_context = tool.Drawing.get_annotation_context(tool.Drawing.get_drawing_target_view(drawing), object_type)
element = tool.Drawing.run_root_assign_class(
obj=obj,
ifc_class="IfcTypeProduct",
predefined_type=object_type,
should_add_representation=create_representation,
context=ifc_context,
ifc_representation_class=tool.Drawing.get_ifc_representation_class(object_type),
)
element.ApplicableOccurrence = f"IfcAnnotation/{object_type}"
tool.Blender.select_and_activate_single_object(context, obj)
# TODO: move to operator
def create_annotation_occurence(context):
# object_type = context.scene.BIMAnnotationProperties.object_type
def create_annotation_occurrence(context):
props = context.scene.BIMAnnotationProperties
relating_type = tool.Ifc.get().by_id(int(props.relating_type_id))
object_type = props.object_type
@@ -152,9 +189,8 @@ def create_annotation_occurence(context):
def create_annotation():
props = bpy.context.scene.BIMAnnotationProperties
create_type_occurence = props.relating_type_id != "0"
if create_type_occurence:
create_annotation_occurence(bpy.context)
if props.relating_type_id != "0":
create_annotation_occurrence(bpy.context)
else:
object_type = props.object_type
bpy.ops.bim.add_annotation(object_type=object_type, data_type=ANNOTATION_TYPES_DATA[object_type][-1])
@@ -171,6 +207,11 @@ class AnnotationToolUI:
row.label(text="No IFC Project", icon="ERROR")
return
drawing = tool.Ifc.get_entity(context.scene.camera)
if not drawing:
row.label(text="No Active Drawing", icon="ERROR")
return
if not AnnotationData.is_loaded:
AnnotationData.load()
@@ -183,13 +224,11 @@ class AnnotationToolUI:
@classmethod
def draw_create_object_interface(cls):
row = cls.layout.row(align=True)
op, row = add_layout_hotkey_operator(cls.layout, "Add Type", "S_C", "Create a new annotation type")
selected_icon = "CHECKBOX_HLT" if cls.props.create_representation_for_type else "CHECKBOX_DEHLT"
row.prop(cls.props, "create_representation_for_type", text="", icon=selected_icon)
row.prop(bpy.context.scene.DocProperties, "should_draw_decorations", text="Viewport Annotations")
@classmethod
def draw_edit_object_interface(cls, context):
if DecoratorData.get_ifc_text_data(bpy.context.object):
if DecoratorData.get_ifc_text_data(bpy.context.active_object):
add_layout_hotkey_operator(cls.layout, "Edit Text", "S_E", "")
@classmethod
@@ -204,21 +243,20 @@ class AnnotationToolUI:
row = cls.layout.row(align=True)
row.label(text="", icon="FILE_3D")
prop_with_search(row, cls.props, "relating_type_id", text="")
row.operator("bim.launch_annotation_type_manager", icon="LIGHTPROBE_GRID", text="")
create_type_occurence = cls.props.relating_type_id != "0"
label = "Add Type Occurence" if create_type_occurence else "Add Annotation"
add_layout_hotkey_operator(cls.layout, label, "S_A", "Create a new annotation")
add_layout_hotkey_operator(cls.layout, "Add", "S_A", "Create a new annotation")
if object_type in ("TEXT", "STAIR_ARROW"):
if object_type in tool.Drawing.ANNOTATION_TYPES_SUPPORT_SETUP:
add_layout_hotkey_operator(
cls.layout,
"Bulk Tag",
"S_T",
"Create new annotations and automatically adjust them to the selected objects",
)
add_layout_hotkey_operator(
cls.layout, "Readjust", "S_G", "Readjust tags based on the products they are assigned to"
)
add_layout_hotkey_operator(
cls.layout, "Readjust", "S_G", "Readjust tags based on the products they are assigned to"
)
class Hotkey(bpy.types.Operator, Operator):
@@ -250,23 +288,27 @@ class Hotkey(bpy.types.Operator, Operator):
def hotkey_S_T(self):
props = bpy.context.scene.BIMAnnotationProperties
object_type = props.object_type
annotation_type = props.object_type
if annotation_type not in tool.Drawing.ANNOTATION_TYPES_SUPPORT_SETUP:
self.report({"ERROR"}, f"Annotation type {annotation_type} is not supported for tagging.")
return
related_objects = bpy.context.selected_objects
for related_object in related_objects:
create_annotation()
obj = bpy.context.active_object
bpy.ops.object.mode_set(mode="OBJECT")
tool.Drawing.setup_annotation_object(obj, object_type, related_object)
tool.Drawing.setup_annotation_object(obj, annotation_type, related_object)
def hotkey_S_A(self):
create_annotation()
def hotkey_S_E(self):
if not bpy.context.object:
if not bpy.context.active_object:
return
if DecoratorData.get_ifc_text_data(bpy.context.object):
if DecoratorData.get_ifc_text_data(bpy.context.active_object):
bpy.ops.bim.edit_text_popup()
def hotkey_S_G(self):
@@ -275,12 +317,15 @@ class Hotkey(bpy.types.Operator, Operator):
if not element or not element.is_a("IfcAnnotation"):
continue
annotation_type = element.ObjectType
if annotation_type not in tool.Drawing.ANNOTATION_TYPES_SUPPORT_SETUP:
self.report({"ERROR"}, f"Annotation type {annotation_type} is not supported for readjustment.")
continue
related_product = tool.Drawing.get_assigned_product(element)
if not related_product:
self.report({"ERROR"}, "Selected annotation has no product assigned.")
continue
related_object = tool.Ifc.get_object(related_product)
tool.Drawing.setup_annotation_object(obj, element.ObjectType, related_object)
def hotkey_S_C(self):
create_annotation_type(bpy.context)
tool.Drawing.setup_annotation_object(obj, annotation_type, related_object)
@@ -29,6 +29,9 @@ classes = (
operator.OverrideDuplicateMoveLinked,
operator.OverrideDuplicateMoveLinkedMacro,
operator.OverrideDuplicateMoveMacro,
operator.OverrideDuplicateMoveAggregate,
operator.OverrideDuplicateMoveAggregateMacro,
operator.RefreshAggregate,
operator.OverrideJoin,
operator.OverrideModeSetEdit,
operator.OverrideModeSetObject,
@@ -58,38 +61,58 @@ def register():
operator.OverrideDuplicateMoveMacro.define("TRANSFORM_OT_translate")
operator.OverrideDuplicateMoveLinkedMacro.define("BIM_OT_override_object_duplicate_move_linked")
operator.OverrideDuplicateMoveLinkedMacro.define("TRANSFORM_OT_translate")
operator.OverrideDuplicateMoveAggregateMacro.define("BIM_OT_override_object_duplicate_move_aggregate")
operator.OverrideDuplicateMoveAggregateMacro.define("TRANSFORM_OT_translate")
bpy.types.Object.BIMGeometryProperties = bpy.props.PointerProperty(type=prop.BIMObjectGeometryProperties)
bpy.types.Scene.BIMGeometryProperties = bpy.props.PointerProperty(type=prop.BIMGeometryProperties)
bpy.types.OBJECT_PT_transform.append(ui.BIM_PT_transform)
bpy.types.VIEW3D_MT_object.append(ui.object_menu)
bpy.types.OUTLINER_MT_object.append(ui.outliner_menu)
bpy.types.VIEW3D_MT_object_context_menu.append(ui.object_menu)
wm = bpy.context.window_manager
if wm.keyconfigs.addon:
km = wm.keyconfigs.addon.keymaps.new(name="Object Mode", space_type="EMPTY")
kmi = km.keymap_items.new("bim.override_object_join", "J", "PRESS", ctrl=True)
addon_keymaps.append((km, kmi))
kmi = km.keymap_items.new("bim.override_object_duplicate_move_macro", "D", "PRESS", shift=True)
addon_keymaps.append((km, kmi))
kmi = km.keymap_items.new("bim.override_object_duplicate_move_linked_macro", "D", "PRESS", alt=True)
addon_keymaps.append((km, kmi))
kmi = km.keymap_items.new("bim.override_object_duplicate_move_aggregate_macro", "D", "PRESS", ctrl=True, shift=True)
addon_keymaps.append((km, kmi))
kmi = km.keymap_items.new("bim.override_paste_buffer", "V", "PRESS", ctrl=True)
addon_keymaps.append((km, kmi))
kmi = km.keymap_items.new("bim.override_mode_set_edit", "TAB", "PRESS")
addon_keymaps.append((km, kmi))
kmi = km.keymap_items.new("bim.override_object_delete", "X", "PRESS")
addon_keymaps.append((km, kmi))
kmi = km.keymap_items.new("bim.override_object_delete", "DEL", "PRESS")
kmi.properties.confirm = False
addon_keymaps.append((km, kmi))
km = wm.keyconfigs.addon.keymaps.new(name="Mesh", space_type="EMPTY")
kmi = km.keymap_items.new("bim.override_mode_set_object", "TAB", "PRESS")
addon_keymaps.append((km, kmi))
km = wm.keyconfigs.addon.keymaps.new(name="Curve", space_type="EMPTY")
kmi = km.keymap_items.new("bim.override_mode_set_object", "TAB", "PRESS")
addon_keymaps.append((km, kmi))
km = wm.keyconfigs.addon.keymaps.new(name="Outliner", space_type="OUTLINER")
kmi = km.keymap_items.new("bim.override_paste_buffer", "V", "PRESS", ctrl=True)
addon_keymaps.append((km, kmi))
kmi = km.keymap_items.new("bim.override_outliner_delete", "X", "PRESS")
addon_keymaps.append((km, kmi))
kmi = km.keymap_items.new("bim.override_outliner_delete", "DEL", "PRESS")
addon_keymaps.append((km, kmi))
def unregister():
bpy.types.VIEW3D_MT_object.remove(ui.object_menu)
bpy.types.OBJECT_PT_transform.remove(ui.BIM_PT_transform)
bpy.types.OUTLINER_MT_object.remove(ui.outliner_menu)
bpy.types.VIEW3D_MT_object_context_menu.remove(ui.outliner_menu)
del bpy.types.Scene.BIMGeometryProperties
del bpy.types.Object.BIMGeometryProperties
wm = bpy.context.window_manager
@@ -111,10 +111,13 @@ class ConnectionsData:
for rel in connected_to:
if element.is_a("IfcDistributionPort"):
related_element = rel.RelatedPort
related_element_connection_type = ""
else:
related_element = rel.RelatedElement
if element.is_a("IfcRelConnectsPathElements"):
related_element_connection_type = rel.RelatedConnectionType
else:
related_element_connection_type = ""
results.append(
{
@@ -128,10 +131,13 @@ class ConnectionsData:
for rel in connected_from:
if element.is_a("IfcDistributionPort"):
relating_element = rel.RelatingPort
relating_element_connection_type = ""
else:
relating_element = rel.RelatingElement
if element.is_a("IfcRelConnectsPathElements"):
relating_element_connection_type = rel.RelatingConnectionType
else:
relating_element_connection_type = ""
results.append(
{
@@ -20,18 +20,20 @@ import bpy
import bmesh
import logging
import numpy as np
import json
import ifcopenshell
import ifcopenshell.util.unit
import ifcopenshell.util.element
import ifcopenshell.util.representation
import ifcopenshell.api
import blenderbim.core.geometry as core
import blenderbim.core.aggregate
import blenderbim.core.style
import blenderbim.core.root
import blenderbim.core.drawing
import blenderbim.tool as tool
import blenderbim.bim.handler
from mathutils import Vector
from mathutils import Vector, Matrix
from blenderbim.bim import import_ifc
from blenderbim.bim.ifc import IfcStore
@@ -59,22 +61,58 @@ class AddRepresentation(bpy.types.Operator, Operator):
bl_idname = "bim.add_representation"
bl_label = "Add Representation"
bl_options = {"REGISTER", "UNDO"}
representation_conversion_method: bpy.props.EnumProperty(
items=[
("OUTLINE", "Trace Outline", ""),
("BOX", "Bounding Box", ""),
("PROJECT", "Full Representation", ""),
],
name="Representation Conversion Method",
)
def _execute(self, context):
ifc_context = int(context.active_object.BIMGeometryProperties.contexts or "0") or None
if ifc_context:
ifc_context = tool.Ifc.get().by_id(ifc_context)
obj = context.active_object
props = obj.BIMGeometryProperties
ifc_context = int(props.contexts or "0") or None
if not ifc_context:
return
ifc_context = tool.Ifc.get().by_id(ifc_context)
if self.representation_conversion_method == "OUTLINE":
if ifc_context.ContextType == "Plan":
data = tool.Geometry.generate_outline_mesh(obj, axis="+Z")
elif ifc_context.ContextIdentifier == "Profile":
data = tool.Geometry.generate_outline_mesh(obj, axis="-Y")
else:
data = tool.Geometry.generate_outline_mesh(obj, axis="+Z")
tool.Geometry.change_object_data(obj, data, is_global=True)
elif self.representation_conversion_method == "BOX":
if ifc_context.ContextType == "Plan":
data = tool.Geometry.generate_2d_box_mesh(obj, axis="Z")
elif ifc_context.ContextIdentifier == "Profile":
data = tool.Geometry.generate_2d_box_mesh(obj, axis="Y")
else:
data = tool.Geometry.generate_3d_box_mesh(obj)
tool.Geometry.change_object_data(obj, data, is_global=True)
core.add_representation(
tool.Ifc,
tool.Geometry,
tool.Style,
tool.Surveyor,
obj=context.active_object,
obj=obj,
context=ifc_context,
ifc_representation_class=None,
profile_set_usage=None,
)
def invoke(self, context, event):
return context.window_manager.invoke_props_dialog(self)
def draw(self, context):
row = self.layout.row()
row.prop(self, "representation_conversion_method", text="")
class SelectConnection(bpy.types.Operator, Operator):
bl_idname = "bim.select_connection"
@@ -415,7 +453,11 @@ class OverrideDelete(bpy.types.Operator):
if self.is_batch:
ifcopenshell.util.element.batch_remove_deep2(tool.Ifc.get())
for obj in context.selected_objects:
if tool.Ifc.get_entity(obj):
element = tool.Ifc.get_entity(obj)
if element:
if ifcopenshell.util.element.get_pset(element, "BBIM_Array"):
self.report({"INFO"}, "Elements that are part of an array cannot be deleted.")
return {"FINISHED"}
tool.Geometry.delete_ifc_object(obj)
else:
bpy.data.objects.remove(obj)
@@ -445,6 +487,7 @@ class OverrideOutlinerDelete(bpy.types.Operator):
bl_label = "IFC Delete"
bl_options = {"REGISTER", "UNDO"}
hierarchy: bpy.props.BoolProperty(default=False)
is_batch: bpy.props.BoolProperty(name="Is Batch", default=False)
@classmethod
def poll(cls, context):
@@ -477,7 +520,26 @@ class OverrideOutlinerDelete(bpy.types.Operator):
bpy.data.collections.remove(collection)
return {"FINISHED"}
def invoke(self, context, event):
if tool.Ifc.get():
total_elements = len(tool.Ifc.get().wrapped_data.entity_names())
total_polygons = sum([len(o.data.polygons) for o in context.selected_objects if o.type == "MESH"])
# These numbers are a bit arbitrary, but basically batching is only
# really necessary on large models and large geometry removals.
self.is_batch = total_elements > 500000 and total_polygons > 2000
if self.is_batch:
return context.window_manager.invoke_props_dialog(self)
return self.execute(context)
def draw(self, context):
row = self.layout.row()
row.label(text="Warning: Faster deletion will use more memory.", icon="ERROR")
row = self.layout.row()
row.prop(self, "is_batch", text="Enable Faster Deletion")
def _execute(self, context):
if self.is_batch:
ifcopenshell.util.element.batch_remove_deep2(tool.Ifc.get())
objects_to_delete = set()
collections_to_delete = set()
for item in context.selected_ids:
@@ -490,8 +552,20 @@ class OverrideOutlinerDelete(bpy.types.Operator):
elif item.bl_rna.identifier == "Object":
objects_to_delete.add(bpy.data.objects.get(item.name))
for obj in objects_to_delete:
# This is the only difference
tool.Geometry.delete_ifc_object(obj)
if tool.Ifc.get_entity(obj):
tool.Geometry.delete_ifc_object(obj)
else:
bpy.data.objects.remove(obj)
for collection in collections_to_delete:
bpy.data.collections.remove(collection)
if self.is_batch:
old_file = tool.Ifc.get()
old_file.end_transaction()
new_file = ifcopenshell.util.element.unbatch_remove_deep2(tool.Ifc.get())
new_file.begin_transaction()
tool.Ifc.set(new_file)
self.transaction_data = {"old_file": old_file, "new_file": new_file}
IfcStore.add_transaction_operation(self)
return {"FINISHED"}
def get_collection_objects_and_children(self, collection):
@@ -506,6 +580,14 @@ class OverrideOutlinerDelete(bpy.types.Operator):
children = children.union(collection.children)
return {"objects": objects, "children": children}
def rollback(self, data):
tool.Ifc.set(data["old_file"])
data["old_file"].undo()
def commit(self, data):
data["old_file"].redo()
tool.Ifc.set(data["new_file"])
class OverrideDuplicateMoveMacro(bpy.types.Macro):
bl_idname = "bim.override_object_duplicate_move_macro"
@@ -552,6 +634,14 @@ class OverrideDuplicateMove(bpy.types.Operator):
relationships = tool.Root.get_decomposition_relationships(context.selected_objects)
old_to_new = {}
for obj in context.selected_objects:
element = tool.Ifc.get_entity(obj)
if element and element.is_a("IfcAnnotation") and element.ObjectType == "DRAWING":
continue # For now, don't copy drawings until we stabilise a bit more. It's tricky.
# Prior to duplicating, sync the object placement to make decomposition recreation more stable.
if tool.Ifc.is_moved(obj):
blenderbim.core.geometry.edit_object_placement(tool.Ifc, tool.Geometry, tool.Surveyor, obj=obj)
new_obj = obj.copy()
if obj.data:
new_obj.data = obj.data.copy()
@@ -620,6 +710,10 @@ class OverrideDuplicateMoveLinked(bpy.types.Operator):
relationships = tool.Root.get_decomposition_relationships(context.selected_objects)
old_to_new = {}
for obj in context.selected_objects:
# Prior to duplicating, sync the object placement to make decomposition recreation more stable.
if tool.Ifc.is_moved(obj):
blenderbim.core.geometry.edit_object_placement(tool.Ifc, tool.Geometry, tool.Surveyor, obj=obj)
new_obj = obj.copy()
if obj.data:
new_obj.data = obj.data.copy()
@@ -643,6 +737,442 @@ class OverrideDuplicateMoveLinked(bpy.types.Operator):
return {"FINISHED"}
class OverrideDuplicateMoveAggregateMacro(bpy.types.Macro):
bl_idname = "bim.override_object_duplicate_move_aggregate_macro"
bl_label = "IFC Duplicate Objects Aggregate"
bl_options = {"REGISTER", "UNDO"}
class OverrideDuplicateMoveAggregate(bpy.types.Operator):
bl_idname = "bim.override_object_duplicate_move_aggregate"
bl_label = "IFC Duplicate Objects Aggregate"
bl_options = {"REGISTER", "UNDO"}
is_interactive: bpy.props.BoolProperty(name="Is Interactive", default=True)
@classmethod
def poll(cls, context):
return len(context.selected_objects) > 0
def execute(self, context):
# Deep magick from the dawn of time
if IfcStore.get_file():
IfcStore.execute_ifc_operator(self, context)
if self.new_active_obj:
context.view_layer.objects.active = self.new_active_obj
return {"FINISHED"}
new_active_obj = None
for obj in context.selected_objects:
new_obj = obj.copy()
if obj.data:
new_obj.data = obj.data.copy()
if obj == context.active_object:
new_active_obj = new_obj
for collection in obj.users_collection:
collection.objects.link(new_obj)
obj.select_set(False)
new_obj.select_set(True)
if new_active_obj:
context.view_layer.objects.active = new_active_obj
return {"FINISHED"}
def _execute(self, context):
self.new_active_obj = None
old_to_new = {}
### Adding the assembly data
def add_assembly_data(element, parent, data_to_add):
pset = ifcopenshell.util.element.get_pset(element, "BBIM_Aggregate_Data")
data = [data_to_add]
if pset:
if parent == None:
ifcopenshell.api.run(
"pset.edit_pset",
tool.Ifc.get(),
pset=tool.Ifc.get().by_id(pset["id"]),
properties={"Data": json.dumps(data)},
)
else:
ifcopenshell.api.run(
"pset.edit_pset",
tool.Ifc.get(),
pset=tool.Ifc.get().by_id(pset["id"]),
properties={"Parent": parent.GlobalId, "Data": json.dumps(data)},
)
else:
pset = ifcopenshell.api.run(
"pset.add_pset", tool.Ifc.get(), product=element, name="BBIM_Aggregate_Data"
)
ifcopenshell.api.run(
"pset.edit_pset",
tool.Ifc.get(),
pset=pset,
properties={"Parent": parent.GlobalId, "Data": json.dumps(data)},
)
def add_child_to_assembly_data(new_entity):
pset = ifcopenshell.util.element.get_pset(new_entity, "BBIM_Aggregate_Data")
parent_element = tool.Ifc.get().by_guid(pset["Parent"])
parent_pset = ifcopenshell.util.element.get_pset(parent_element, "BBIM_Aggregate_Data")
data = json.loads(parent_pset["Data"])
data[0]["children"].append(new_entity.GlobalId)
ifcopenshell.api.run(
"pset.edit_pset",
tool.Ifc.get(),
pset=tool.Ifc.get().by_id(parent_pset["id"]),
properties={"Data": json.dumps(data)},
)
def create_data_structure(entity, level=-1):
level += 1
data_children = {
"children": [],
"instance_of": [],
}
data_parent = {
"children": [],
"instance_of": [entity.GlobalId],
}
if not entity.is_a("IfcElementAssembly"):
return
else:
if level == 0:
add_assembly_data(entity, entity, data_parent)
else:
add_assembly_data(entity, None, data_parent)
parts = ifcopenshell.util.element.get_parts(entity)
for part in parts:
if part.is_a("IfcElementAssembly"):
add_assembly_data(part, entity, data_children)
add_child_to_assembly_data(part)
create_data_structure(part, level)
continue
add_assembly_data(part, entity, data_children)
add_child_to_assembly_data(part)
return
def recreate_data_structure(entity, level=-1):
level += 1
pset = ifcopenshell.util.element.get_pset(entity, "BBIM_Aggregate_Data")
pset_data = json.loads(pset["Data"])[0]
instance_of = pset_data["instance_of"]
data_children = {
"children": [],
"instance_of": [],
}
# Keeps instance ID through all copies
data_parent = {
"children": [],
"instance_of": instance_of,
}
if not entity.is_a("IfcElementAssembly"):
return
else:
if level == 0:
add_assembly_data(entity, entity, data_parent)
else:
add_assembly_data(entity, None, data_parent)
parts = ifcopenshell.util.element.get_parts(entity)
for part in parts:
if part.is_a("IfcElementAssembly"):
add_child_to_assembly_data(part)
recreate_data_structure(part, level)
continue
add_assembly_data(part, entity, data_children)
add_child_to_assembly_data(part)
return
def duplicate_all(obj, level=-1, new_parent=None, parents=[]):
level += 1
entity = tool.Ifc.get_entity(obj)
if level == 0:
new_parent = duplicate_objects(obj)
parents.append(new_parent)
else:
pair = []
pair.append(new_parent)
new_parent = duplicate_objects(obj)
pair.append(new_parent)
parents.append(pair)
if not entity.is_a("IfcElementAssembly"):
return
else:
parts = ifcopenshell.util.element.get_parts(entity)
# Ensures that we are duplication all the IfcElementAssembly
# before duplicating the nested objects
for part in parts:
if part.is_a("IfcElementAssembly"):
pass
else:
part_obj = tool.Ifc.get_object(part)
new_part = duplicate_objects(part_obj)
blenderbim.core.aggregate.assign_object(
tool.Ifc,
tool.Aggregate,
tool.Collector,
relating_obj=tool.Ifc.get_object(new_parent),
related_obj=tool.Ifc.get_object(new_part),
)
for part in parts:
if part.is_a("IfcElementAssembly"):
part_obj = tool.Ifc.get_object(part)
# Recursion Call
duplicate_all(part_obj, level, new_parent, parents)
if level == 0:
for p in parents[1:]:
blenderbim.core.aggregate.assign_object(
tool.Ifc,
tool.Aggregate,
tool.Collector,
relating_obj=tool.Ifc.get_object(p[0]),
related_obj=tool.Ifc.get_object(p[1]),
)
return new_parent
return
def duplicate_objects(obj, is_root=False):
new_obj = obj.copy()
if obj.data:
new_obj.data = obj.data.copy()
if obj == context.active_object:
self.new_active_obj = new_obj
for collection in obj.users_collection:
collection.objects.link(new_obj)
obj.select_set(False)
new_obj.select_set(True)
# This is needed to make sure the new object gets unlink from
# the old object assembly collection
new_obj.BIMObjectProperties.collection = None
# Copy the actual class
new_entity = blenderbim.core.root.copy_class(
tool.Ifc, tool.Collector, tool.Geometry, tool.Root, obj=new_obj
)
if new_entity:
# Checks if the object belongs to an Ifc Array
array_pset = ifcopenshell.util.element.get_pset(new_entity, "BBIM_Array")
if array_pset:
array_pset = tool.Ifc.get().by_id(array_pset["id"])
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), product=new_entity, pset=array_pset)
blenderbim.core.aggregate.unassign_object(
tool.Ifc,
tool.Aggregate,
tool.Collector,
relating_obj=tool.Ifc.get_object(selected_root_entity),
related_obj=tool.Ifc.get_object(new_entity),
)
old_to_new[tool.Ifc.get_entity(obj)] = [new_entity]
return new_entity
### Check if only one element and it's assembly
if len(context.selected_objects) != 1:
return {"FINISHED"}
selected_obj = context.selected_objects[0]
selected_root_entity = tool.Ifc.get_entity(selected_obj)
if not selected_root_entity.is_a("IfcElementAssembly"):
return {"FINISHED"}
pset = ifcopenshell.util.element.get_pset(selected_root_entity, "BBIM_Aggregate_Data")
if not pset:
create_data_structure(selected_root_entity)
pset = ifcopenshell.util.element.get_pset(selected_root_entity, "BBIM_Aggregate_Data")
selected_root_parent = selected_root_entity
new_root_entity = duplicate_all(selected_obj)
recreate_data_structure(new_root_entity)
old_objs = []
for old, new in old_to_new.items():
old_objs.append(tool.Ifc.get_object(old))
relationships = tool.Root.get_decomposition_relationships(old_objs)
tool.Root.recreate_decompositions(relationships, old_to_new)
blenderbim.bim.handler.purge_module_data()
return {"FINISHED"}
class RefreshAggregate(bpy.types.Operator):
bl_idname = "bim.refresh_aggregate"
bl_label = "IFC Refresh Aggregate"
bl_options = {"REGISTER", "UNDO"}
@classmethod
def poll(cls, context):
return len(context.selected_objects) > 0
def execute(self, context):
# Deep magick from the dawn of time
if IfcStore.get_file():
IfcStore.execute_ifc_operator(self, context)
return {"FINISHED"}
return {"FINISHED"}
def _execute(self, context):
self.new_active_obj = None
old_to_new = {}
def remove_objects(entity, level=-1, parents=[]):
level += 1
if level == 0:
parents = [entity]
parts = ifcopenshell.util.element.get_parts(entity)
for part in parts:
if part.is_a("IfcElementAssembly"):
parents.append(part)
remove_objects(part, level, parents)
continue
else:
part_obj = tool.Ifc.get_object(part)
if part_obj:
tool.Geometry.delete_ifc_object(part_obj)
return parents
def duplicate_children(entity):
pset = ifcopenshell.util.element.get_pset(entity, "BBIM_Aggregate_Data")
pset_data = json.loads(pset["Data"])[0]
instance_of = pset_data["instance_of"][0]
instance_entity = tool.Ifc.get().by_guid(instance_of)
if not instance_entity.is_a("IfcElementAssembly"):
return
else:
parts = ifcopenshell.util.element.get_parts(instance_entity)
for part in parts:
if part.is_a("IfcElementAssembly"):
pass
else:
part_obj = tool.Ifc.get_object(part)
new_part = duplicate_objects(part_obj)
blenderbim.core.aggregate.assign_object(
tool.Ifc,
tool.Aggregate,
tool.Collector,
relating_obj=tool.Ifc.get_object(entity),
related_obj=tool.Ifc.get_object(new_part),
)
def duplicate_objects(obj, is_root=False):
new_obj = obj.copy()
if obj.data:
new_obj.data = obj.data.copy()
if obj == context.active_object:
self.new_active_obj = new_obj
for collection in obj.users_collection:
collection.objects.link(new_obj)
obj.select_set(False)
new_obj.select_set(True)
# Copy the actual class
new_entity = blenderbim.core.root.copy_class(
tool.Ifc, tool.Collector, tool.Geometry, tool.Root, obj=new_obj
)
if new_entity:
# Checks if the object belongs to an Ifc Array
array_pset = ifcopenshell.util.element.get_pset(new_entity, "BBIM_Array")
if array_pset:
array_pset = tool.Ifc.get().by_id(array_pset["id"])
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), product=new_entity, pset=array_pset)
blenderbim.core.aggregate.unassign_object(
tool.Ifc,
tool.Aggregate,
tool.Collector,
relating_obj=obj,
related_obj=tool.Ifc.get_object(new_entity),
)
old_to_new[tool.Ifc.get_entity(obj)] = [new_entity]
return new_entity
if len(context.selected_objects) != 1:
return {"FINISHED"}
selected_root_obj = context.selected_objects[0]
selected_root_entity = tool.Ifc.get_entity(selected_root_obj)
if not selected_root_entity.is_a("IfcElementAssembly"):
return {"FINISHED"}
pset = ifcopenshell.util.element.get_pset(selected_root_entity, "BBIM_Aggregate_Data")
if not pset:
return {"FINISHED"}
pset_data = json.loads(pset["Data"])[0]
instance_of = pset_data["instance_of"][0]
original_root_entity = tool.Ifc.get().by_guid(instance_of)
if original_root_entity == selected_root_entity:
return {"FINISHED"}
parents = remove_objects(selected_root_entity)
original_root_object = tool.Ifc.get_object(original_root_entity)
selected_matrix = selected_root_obj.matrix_world
original_matrix = original_root_object.matrix_world
for parent in parents:
duplicate_children(parent)
old_objs = []
for old, new in old_to_new.items():
old_objs.append(tool.Ifc.get_object(old))
new_obj = tool.Ifc.get_object(new[0])
matrix_diff = new_obj.matrix_world @ original_matrix
new_matrix = selected_matrix @ matrix_diff
new_obj.matrix_world = new_matrix
relationships = tool.Root.get_decomposition_relationships(old_objs)
tool.Root.recreate_decompositions(relationships, old_to_new)
blenderbim.bim.handler.purge_module_data()
return {"FINISHED"}
class OverrideJoin(bpy.types.Operator, Operator):
bl_idname = "bim.override_object_join"
bl_label = "IFC Join"
@@ -753,6 +1283,7 @@ class OverridePasteBuffer(bpy.types.Operator):
class OverrideModeSetEdit(bpy.types.Operator):
bl_description = "Switch from Object mode to Edit mode"
bl_idname = "bim.override_mode_set_edit"
bl_label = "IFC Mode Set Edit"
bl_options = {"REGISTER", "UNDO"}
@@ -761,7 +1292,7 @@ class OverrideModeSetEdit(bpy.types.Operator):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
objs = context.selected_objects or ([context.active_object] if context.active_object else [])
selected_objs = context.selected_objects or ([context.active_object] if context.active_object else [])
active_obj = context.active_object
if context.active_object:
@@ -771,19 +1302,15 @@ class OverrideModeSetEdit(bpy.types.Operator):
if element and element.is_a("IfcRelSpaceBoundary"):
return bpy.ops.bim.enable_editing_boundary_geometry()
for obj in objs:
for obj in selected_objs:
if not obj:
continue
if not obj.data:
obj.select_set(False)
continue
element = tool.Ifc.get_entity(obj)
if not element:
continue
# We are switching from OBJECT to EDIT mode.
usage_type = tool.Model.get_usage_type(element)
if usage_type is not None and usage_type not in ("PROFILE", "LAYER3"):
# Parametric objects shall not be edited as meshes as they
@@ -791,35 +1318,37 @@ class OverrideModeSetEdit(bpy.types.Operator):
# constraints.
obj.select_set(False)
continue
representation = tool.Geometry.get_active_representation(obj)
if not representation:
continue
if (
tool.Pset.get_element_pset(element, "BBIM_Door")
or tool.Pset.get_element_pset(element, "BBIM_Window")
or tool.Pset.get_element_pset(element, "BBIM_Stair")
):
if tool.Blender.Modifier.is_modifier_with_non_editable_path(element):
obj.select_set(False)
continue
is_profile = True
if usage_type == "PROFILE":
if len(context.selected_objects) == 1:
bpy.ops.bim.hotkey(hotkey="A_E", description="")
return {"FINISHED"}
else:
self.report({"INFO"}, "Only a single profile-based representation can be edited at a time.")
obj.select_set(False)
continue
if (
operator = lambda: bpy.ops.bim.hotkey(hotkey="A_E")
elif (
tool.Geometry.is_profile_based(obj.data)
or usage_type == "LAYER3"
or tool.Geometry.is_swept_profile(representation)
or tool.Pset.get_element_pset(element, "BBIM_Roof")
or tool.Pset.get_element_pset(element, "BBIM_Railing")
):
if len(context.selected_objects) == 1:
bpy.ops.bim.hotkey(hotkey="S_E", description="")
operator = lambda: bpy.ops.bim.hotkey(hotkey="S_E")
elif tool.Blender.Modifier.is_editing_parameters(obj):
# This should go BEFORE the modifiers
self.report({"INFO"}, "Can't edit path while the modifier parameters are being modified")
obj.select_set(False)
continue
elif tool.Blender.Modifier.is_roof(element):
operator = lambda: bpy.ops.bim.enable_editing_roof_path()
elif tool.Blender.Modifier.is_railing(element):
operator = lambda: bpy.ops.bim.enable_editing_railing_path()
else:
is_profile = False
if is_profile:
if len(context.selected_objects) == 1 and context.active_object == context.selected_objects[0]:
tool.Blender.select_and_activate_single_object(context, obj)
operator()
return {"FINISHED"}
else:
self.report({"INFO"}, "Only a single profile-based representation can be edited at a time.")
@@ -840,30 +1369,34 @@ class OverrideModeSetEdit(bpy.types.Operator):
should_sync_changes_first=False,
apply_openings=False,
)
tool.Geometry.dissolve_triangulated_edges(obj)
obj.data.BIMMeshProperties.mesh_checksum = tool.Geometry.get_mesh_checksum(obj.data)
else:
obj.select_set(False)
continue
if not context.selected_objects or len(context.selected_objects) != len(objs):
if not context.selected_objects or len(context.selected_objects) != len(selected_objs):
# We are trying to edit at least one non-mesh-like object : Display a hint to the user
self.report({"INFO"}, "Only mesh-compatible representations may be edited in edit mode.")
self.report({"INFO"}, "Only mesh-compatible representations may be edited concurrently in edit mode.")
if context.active_object not in context.selected_objects:
# The active object is non-mesh-like. Set a valid object (or None) as active
context.view_layer.objects.active = context.selected_objects[0] if context.selected_objects else None
if context.active_object:
bpy.ops.object.mode_set(mode="EDIT", toggle=True)
else:
# restore the selection if nothing worked
for obj in objs:
obj.select_set(True)
context.view_layer.objects.active = active_obj
return tool.Blender.toggle_edit_mode(context)
# Restore the selection if nothing worked
for obj in selected_objs:
obj.select_set(True)
context.view_layer.objects.active = active_obj
return {"FINISHED"}
def invoke(self, context, event):
return IfcStore.execute_ifc_operator(self, context, is_invoke=True)
def _invoke(self, context, event):
if not tool.Ifc.get():
return bpy.ops.object.mode_set(mode="EDIT", toggle=True)
return tool.Blender.toggle_edit_mode(context)
return self.execute(context)
@@ -911,6 +1444,9 @@ class OverrideModeSetObject(bpy.types.Operator):
row.label(text="No Geometry Found: Object will revert to previous state.")
def invoke(self, context, event):
return IfcStore.execute_ifc_operator(self, context, is_invoke=True)
def _invoke(self, context, event):
self.is_valid = True
self.should_save = True
@@ -943,10 +1479,10 @@ class OverrideModeSetObject(bpy.types.Operator):
profile = tool.Ifc.get().by_id(profile_id)
if tool.Ifc.get_object(profile): # We are editing an arbitrary profile
bpy.ops.bim.edit_arbitrary_profile()
elif tool.Pset.get_element_pset(element, "BBIM_Railing"):
bpy.ops.bim.cad_hotkey(hotkey="S_Q")
elif tool.Pset.get_element_pset(element, "BBIM_Roof"):
bpy.ops.bim.cad_hotkey(hotkey="S_Q")
elif tool.Blender.Modifier.is_railing(element):
bpy.ops.bim.finish_editing_railing_path()
elif tool.Blender.Modifier.is_roof(element):
bpy.ops.bim.finish_editing_roof_path()
elif tool.Model.get_usage_type(element) == "PROFILE":
bpy.ops.bim.edit_extrusion_axis()
else:
@@ -17,6 +17,7 @@
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import bpy
import blenderbim.tool as tool
from bpy.types import Panel
from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.helper import prop_with_search
@@ -24,20 +25,26 @@ from blenderbim.bim.module.geometry.data import RepresentationsData, Connections
def object_menu(self, context):
self.layout.separator()
self.layout.operator("bim.override_object_duplicate_move", icon="PLUGIN")
self.layout.operator("bim.override_object_delete", icon="PLUGIN")
self.layout.operator("bim.override_paste_buffer", icon="PLUGIN")
def outliner_menu(self, context):
self.layout.separator()
self.layout.operator("bim.override_outliner_delete", icon="X")
class BIM_PT_representations(Panel):
bl_label = "IFC Representations"
bl_label = "Representations"
bl_idname = "BIM_PT_representations"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "object"
bl_context = "scene"
bl_order = 1
bl_parent_id = "BIM_PT_geometry_object"
bl_parent_id = "BIM_PT_tab_representations"
bl_options = {"HIDE_HEADER"}
@classmethod
def poll(cls, context):
@@ -54,20 +61,24 @@ class BIM_PT_representations(Panel):
layout = self.layout
props = context.active_object.BIMObjectProperties
if not RepresentationsData.data["representations"]:
layout.label(text="No representations found")
row = layout.row(align=True)
prop_with_search(row, context.active_object.BIMGeometryProperties, "contexts", text="")
row.operator("bim.add_representation", icon="ADD", text="")
if not RepresentationsData.data["representations"]:
layout.label(text="No Representations Found")
for representation in RepresentationsData.data["representations"]:
row = self.layout.row(align=True)
row.label(text=representation["ContextType"])
row.label(text=representation["ContextIdentifier"])
row.label(text=representation["TargetView"])
row.label(text=representation["RepresentationType"])
op = row.operator("bim.switch_representation", icon="FILE_REFRESH" if representation["is_active"] else "OUTLINER_DATA_MESH", text="")
op = row.operator(
"bim.switch_representation",
icon="FILE_REFRESH" if representation["is_active"] else "OUTLINER_DATA_MESH",
text="",
)
op.should_switch_all_meshes = True
op.should_reload = True
op.ifc_definition_id = representation["id"]
@@ -76,14 +87,14 @@ class BIM_PT_representations(Panel):
class BIM_PT_connections(Panel):
bl_label = "IFC Connections"
bl_label = "Connections"
bl_idname = "BIM_PT_connections"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "object"
bl_order = 1
bl_parent_id = "BIM_PT_geometry_object"
bl_parent_id = "BIM_PT_tab_geometric_relationships"
@classmethod
def poll(cls, context):
@@ -114,11 +125,14 @@ class BIM_PT_connections(Panel):
class BIM_PT_mesh(Panel):
bl_label = "IFC Representation"
bl_label = "Representation Utilities"
bl_idname = "BIM_PT_mesh"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "data"
bl_order = 2
bl_options = {"DEFAULT_CLOSED"}
bl_parent_id = "BIM_PT_tab_representations"
@classmethod
def poll(cls, context):
@@ -132,43 +146,34 @@ class BIM_PT_mesh(Panel):
def draw(self, context):
if not context.active_object.data:
return
row = self.layout.row()
row.label(text="Advanced Users Only", icon="ERROR")
layout = self.layout
props = context.active_object.data.BIMMeshProperties
row = layout.row()
row.operator("bim.copy_representation")
row.operator("bim.copy_representation", text="Copy Mesh From Active To Selected")
row = layout.row()
row.operator("bim.update_representation")
row = layout.row()
op = row.operator("bim.update_representation", text="Update Mesh As Tessellation")
op = row.operator("bim.update_representation", text="Convert To Tessellation")
op.ifc_representation_class = "IfcTessellatedFaceSet"
row = layout.row()
op = row.operator("bim.update_representation", text="Update Mesh As Rectangle Extrusion")
op = row.operator("bim.update_representation", text="Convert To Rectangle Extrusion")
op.ifc_representation_class = "IfcExtrudedAreaSolid/IfcRectangleProfileDef"
row = layout.row()
op = row.operator("bim.update_representation", text="Update Mesh As Circle Extrusion")
op = row.operator("bim.update_representation", text="Convert To Circle Extrusion")
op.ifc_representation_class = "IfcExtrudedAreaSolid/IfcCircleProfileDef"
row = layout.row()
op = row.operator("bim.update_representation", text="Update Mesh As Arbitrary Extrusion")
op = row.operator("bim.update_representation", text="Convert To Arbitrary Extrusion")
op.ifc_representation_class = "IfcExtrudedAreaSolid/IfcArbitraryClosedProfileDef"
row = layout.row()
op = row.operator("bim.update_representation", text="Update Mesh As Arbitrary Extrusion With Voids")
op = row.operator("bim.update_representation", text="Convert To Arbitrary Extrusion With Voids")
op.ifc_representation_class = "IfcExtrudedAreaSolid/IfcArbitraryProfileDefWithVoids"
row = layout.row()
row.operator("bim.get_representation_ifc_parameters")
for index, ifc_parameter in enumerate(props.ifc_parameters):
row = layout.row(align=True)
row.prop(ifc_parameter, "name", text="")
row.prop(ifc_parameter, "value", text="")
row.operator("bim.update_parametric_representation", icon="FILE_REFRESH", text="").index = index
def BIM_PT_transform(self, context):
if context.active_object and context.active_object.BIMObjectProperties.ifc_definition_id:
@@ -181,7 +186,7 @@ def BIM_PT_transform(self, context):
class BIM_PT_derived_placements(Panel):
bl_label = "IFC Derived Placements"
bl_label = "Derived Placements"
bl_idname = "BIM_PT_derived_placements"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
@@ -216,12 +221,13 @@ class BIM_PT_derived_placements(Panel):
class BIM_PT_workarounds(Panel):
bl_label = "IFC Vendor Workarounds"
bl_label = "Vendor Workarounds"
bl_idname = "BIM_PT_workarounds"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "data"
bl_parent_id = "BIM_PT_tab_geometric_relationships"
@classmethod
def poll(cls, context):
@@ -33,6 +33,7 @@ classes = (
operator.ConvertGlobalToLocal,
operator.GetCursorLocation,
operator.SetCursorLocation,
operator.ConvertAngleToCoordinates,
prop.BIMGeoreferenceProperties,
ui.BIM_PT_gis,
ui.BIM_PT_gis_utilities,
@@ -171,3 +171,19 @@ class ConvertGlobalToLocal(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context):
core.convert_global_to_local(tool.Georeference)
class ConvertAngleToCoordinates(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.convert_angle_to_coord"
bl_label = "Convert Angle To Y Axis"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Convert angle to Y axis"
type: bpy.props.StringProperty()
@classmethod
def poll(cls, context):
file = tool.Ifc.get()
props = context.scene.BIMGeoreferenceProperties
return file and (props.angle_degree_input_x or props.angle_degree_input_y)
def _execute(self, context):
core.convert_angle_to_coord(tool.Georeference, type=self.type)
@@ -37,6 +37,12 @@ class BIMGeoreferenceProperties(PropertyGroup):
projected_crs: CollectionProperty(name="Projected CRS", type=Attribute)
coordinate_input: StringProperty(name="Coordinate Input", description='Formatted "x,y,z" (without quotes)')
coordinate_output: StringProperty(name="Coordinate Output", description='Formatted "x,y,z" (without quotes)')
angle_degree_input_x: FloatProperty(name="Angle Degree Input", description="Angle (in degrees) rel to Easting")
angle_degree_input_y: FloatProperty(name="Angle Degree Input", description="Angle (in degrees) rel to +Y")
x_axis_abscissa_output: StringProperty(name="X Axis Abscissa Ordinate Output", description="X axis abscissa and ordinate", )
x_axis_ordinate_output: StringProperty(name="X Axis Abscissa Ordinate Output", description="X axis abscissa and ordinate", )
y_axis_abscissa_output: StringProperty(name="Y Axis Abscissa Ordinate Output", description="Y axis abscissa and ordinate", )
y_axis_ordinate_output: StringProperty(name="Y Axis Abscissa Ordinate Output", description="Y axis abscissa and ordinate", )
has_blender_offset: BoolProperty(name="Has Blender Offset")
blender_eastings: StringProperty(name="Blender Eastings", default="0")
blender_northings: StringProperty(name="Blender Northings", default="0")
@@ -23,7 +23,7 @@ from blenderbim.bim.module.georeference.data import GeoreferenceData
class BIM_PT_gis(Panel):
bl_label = "IFC Georeferencing"
bl_label = "Georeferencing"
bl_idname = "BIM_PT_gis"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
@@ -167,3 +167,21 @@ class BIM_PT_gis_utilities(Panel):
row = self.layout.row(align=True)
row.operator("bim.convert_local_to_global", text="Local to Global")
row.operator("bim.convert_global_to_local", text="Global to Local")
row = self.layout.row(align=True)
row.label(text="Orientation Calculator", icon="TRACKING_REFINE_FORWARDS")
row = self.layout.row(align=True)
row.prop(props, "angle_degree_input_x", text="Angle (°) rel to Easting")
row.operator("bim.convert_angle_to_coord", text="", icon="FILE_REFRESH").type = "rel_x"
row = self.layout.row(align=True)
row.prop(props, "x_axis_abscissa_output", text="XAxis Abscissa")
row = self.layout.row(align=True)
row.prop(props, "x_axis_ordinate_output", text="XAxis Ordinate")
row = self.layout.row(align=True)
row.prop(props, "angle_degree_input_y", text="Angle (°) rel to to +Y")
row.operator("bim.convert_angle_to_coord", text="", icon="FILE_REFRESH").type = "rel_y"
row = self.layout.row(align=True)
row.prop(props, "y_axis_abscissa_output", text="North Abscissa")
row = self.layout.row(align=True)
row.prop(props, "y_axis_ordinate_output", text="North Ordinate")
@@ -20,13 +20,13 @@ from bpy.types import Panel
class BIM_PT_cityjson_converter(Panel):
bl_label = "IFC CityJSON"
bl_label = "CityJSON"
bl_idname = "BIM_PT_ifccityjson"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_collaboration"
bl_parent_id = "BIM_PT_tab_collaboration"
def draw(self, context):
layout = self.layout
@@ -48,7 +48,7 @@ class LoadGroups(bpy.types.Operator, tool.Ifc.Operator):
new = self.props.groups.add()
new.ifc_definition_id = group.id()
new.name = group.Name or "Unnamed"
new.selection_query = group.Description.split("*selector*")[1] if group.Description else ""
new.selection_query = ""
new.tree_depth = tree_depth
new.has_children = False
new.is_expanded = group.id() in self.expanded_groups
@@ -22,13 +22,13 @@ from blenderbim.bim.module.group.data import GroupsData, ObjectGroupsData
class BIM_PT_groups(Panel):
bl_label = "IFC Groups"
bl_label = "Groups"
bl_idname = "BIM_PT_groups"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_project_setup"
bl_parent_id = "BIM_PT_tab_grouping_and_filtering"
@classmethod
def poll(cls, context):
@@ -69,14 +69,14 @@ class BIM_PT_groups(Panel):
class BIM_PT_object_groups(Panel):
bl_label = "IFC Groups"
bl_label = "Groups"
bl_idname = "BIM_PT_object_groups"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "object"
bl_order = 1
bl_parent_id = "BIM_PT_utilities_object"
bl_parent_id = "BIM_PT_tab_misc"
@classmethod
def poll(cls, context):
@@ -17,7 +17,7 @@ class CreateRepo(bpy.types.Operator):
def poll(cls, context):
IfcGitData.make_sure_is_loaded()
path_ifc = IfcGitData.data["path_ifc"]
if not os.path.isfile(path_ifc):
if not path_ifc or not os.path.isfile(path_ifc):
return False
if IfcGitData.data["repo"]:
# repo already exists
@@ -45,7 +45,7 @@ class AddFileToRepo(bpy.types.Operator):
def poll(cls, context):
IfcGitData.make_sure_is_loaded()
path_ifc = IfcGitData.data["path_ifc"]
if not os.path.isfile(path_ifc):
if not path_ifc or not os.path.isfile(path_ifc):
return False
if not IfcGitData.data["repo"]:
# repo doesn't exist
@@ -6,7 +6,7 @@ from blenderbim.bim.module.ifcgit.data import IfcGitData
class IFCGIT_PT_panel(bpy.types.Panel):
"""Scene Properties panel to interact with IFC repository data"""
bl_label = "IFC Git"
bl_label = "Git"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
@@ -211,6 +211,33 @@ class COMMIT_UL_List(bpy.types.UIList):
layout.label(text=refs + commit.message.split("\n")[0], icon="DECORATE_ANIMATE")
layout.label(text=time.strftime("%c", time.localtime(commit.committed_date)))
def draw_filter(self, context, layout):
# We only need filtering and reverse sort, not reordering by name
row = layout.row(align=True)
row.prop(self, "filter_name", text="")
row.prop(self, "use_filter_invert", text="", icon="ARROW_LEFTRIGHT")
row.prop(self, "use_filter_sort_reverse", text="", icon="SORT_DESC")
def filter_items(self, context, data, propname):
commits = getattr(data, propname)
helper_funcs = bpy.types.UI_UL_list
# Default return values.
flt_flags = []
flt_neworder = list(range(len(commits)))
# Filtering by commit message
if self.filter_name:
flt_flags = helper_funcs.filter_items_by_name(
self.filter_name, self.bitflag_filter_item, commits, "message", reverse=False
)
if not flt_flags:
flt_flags = [self.bitflag_filter_item] * len(commits)
return flt_flags, flt_neworder
class IFCGIT_PT_revision_inspector(bpy.types.Panel):
"""Tool panel to interact with revision history"""
@@ -22,13 +22,13 @@ from blenderbim.bim.module.layer.data import LayersData
class BIM_PT_layers(Panel):
bl_label = "IFC Presentation Layers"
bl_label = "Presentation Layers"
bl_idname = "BIM_PT_layers"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_geometry_object"
bl_parent_id = "BIM_PT_tab_geometric_relationships"
@classmethod
def poll(cls, context):
@@ -27,7 +27,7 @@ class BIM_PT_lca(Panel):
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_integrations"
bl_parent_id = "BIM_PT_tab_sandbox"
def draw(self, context):
props = context.scene.BIMLCAProperties
@@ -23,7 +23,7 @@ from blenderbim.bim.module.library.data import LibrariesData, LibraryReferencesD
class BIM_PT_libraries(Panel):
bl_label = "IFC Libraries"
bl_label = "Libraries"
bl_idname = "BIM_PT_libraries"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
@@ -94,14 +94,14 @@ class BIM_PT_libraries(Panel):
class BIM_PT_library_references(Panel):
bl_label = "IFC Library References"
bl_label = "Library References"
bl_idname = "BIM_PT_library_references"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "object"
bl_order = 1
bl_parent_id = "BIM_PT_misc_object"
bl_parent_id = "BIM_PT_tab_misc"
@classmethod
def poll(cls, context):
@@ -32,9 +32,10 @@ classes = (
operator.CopyMaterial,
operator.DisableEditingAssignedMaterial,
operator.DisableEditingMaterial,
operator.DisableEditingMaterials,
operator.DisableEditingMaterialSetItem,
operator.DisableEditingMaterialSetItemProfile,
operator.DisableEditingMaterials,
operator.DuplicateLayer,
operator.EditAssignedMaterial,
operator.EditMaterial,
operator.EditMaterialSetItem,
@@ -217,10 +217,10 @@ class RemoveConstituent(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context):
constituent = tool.Ifc.get().by_id(self.constituent)
for material_set in layer.ToMaterialConstituentSet:
for material_set in constituent.ToMaterialConstituentSet:
if len(material_set.MaterialConstituents) == 1:
self.report({"ERROR"}, "At least one constituent must exist")
return {"ERROR"}
return {"CANCELLED"}
ifcopenshell.api.run("material.remove_constituent", tool.Ifc.get(), constituent=constituent)
@@ -255,7 +255,7 @@ class RemoveProfile(bpy.types.Operator, tool.Ifc.Operator):
for material_set in profile.ToMaterialProfileSet:
if len(material_set.MaterialProfiles) == 1:
self.report({"ERROR"}, "At least one profile must exist")
return {"ERROR"}
return {"CANCELLED"}
ifcopenshell.api.run("material.remove_profile", tool.Ifc.get(), profile=profile)
@@ -311,7 +311,6 @@ class RemoveLayer(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.remove_layer"
bl_label = "Remove Layer"
bl_options = {"REGISTER", "UNDO"}
obj: bpy.props.StringProperty()
layer: bpy.props.IntProperty()
def _execute(self, context):
@@ -319,10 +318,25 @@ class RemoveLayer(bpy.types.Operator, tool.Ifc.Operator):
for material_set in layer.ToMaterialLayerSet:
if len(material_set.MaterialLayers) == 1:
self.report({"ERROR"}, "At least one layer must exist")
return {"ERROR"}
return {"CANCELLED"}
ifcopenshell.api.run("material.remove_layer", tool.Ifc.get(), layer=layer)
class DuplicateLayer(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.duplicate_layer"
bl_label = "Duplicate Layer"
bl_options = {"REGISTER", "UNDO"}
layer: bpy.props.IntProperty()
def _execute(self, context):
layer = tool.Ifc.get().by_id(self.layer)
new_layer = ifcopenshell.util.element.copy(tool.Ifc.get(), layer)
for material_set in layer.ToMaterialLayerSet:
layers = list(material_set.MaterialLayers)
layers.insert(layers.index(layer), new_layer)
material_set.MaterialLayers = layers
class AddListItem(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.add_list_item"
bl_label = "Add List Item"
@@ -17,6 +17,7 @@
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import blenderbim.bim.helper
import blenderbim.tool as tool
from bpy.types import Panel, UIList
from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.helper import draw_attributes
@@ -25,7 +26,7 @@ from blenderbim.bim.module.material.data import MaterialsData, ObjectMaterialDat
class BIM_PT_materials(Panel):
bl_label = "IFC Materials"
bl_label = "Materials"
bl_idname = "BIM_PT_materials"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
@@ -88,7 +89,7 @@ class BIM_PT_materials(Panel):
blenderbim.bim.helper.draw_attributes(self.props.material_attributes, self.layout)
class BIM_PT_material(Panel):
bl_label = "IFC Material"
bl_label = "Material"
bl_idname = "BIM_PT_material"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
@@ -111,15 +112,17 @@ class BIM_PT_material(Panel):
class BIM_PT_object_material(Panel):
bl_label = "IFC Object Material"
bl_label = "Object Material"
bl_idname = "BIM_PT_object_material"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "object"
bl_parent_id = "BIM_PT_object_metadata"
bl_context = "scene"
bl_parent_id = "BIM_PT_tab_materials"
@classmethod
def poll(cls, context):
if not tool.Blender.is_tab(context, "GEOMETRY"):
return False
if not context.active_object:
return False
props = context.active_object.BIMObjectProperties
@@ -272,6 +275,8 @@ class BIM_PT_object_material(Panel):
op.material_set_item = set_item["id"]
op = row.operator("bim.enable_editing_material_set_item", icon="GREASEPENCIL", text="")
op.material_set_item = set_item["id"]
if ObjectMaterialData.data["set_item_name"] == "layer":
row.operator("bim.duplicate_layer", icon="DUPLICATE", text="").layer = set_item["id"]
op = row.operator(f"bim.remove_{ObjectMaterialData.data['set_item_name']}", icon="X", text="")
if ObjectMaterialData.data["material_class"] == "IfcMaterialList":
setattr(op, "list_item_set", ObjectMaterialData.data["set"]["id"])
@@ -31,6 +31,7 @@ from . import (
stair,
window,
opening,
mep,
pie,
workspace,
profile,
@@ -38,6 +39,7 @@ from . import (
door,
railing,
roof,
mep,
)
classes = (
@@ -45,15 +47,20 @@ classes = (
array.DisableEditingArray,
array.EditArray,
array.EnableEditingArray,
array.ApplyArray,
array.RemoveArray,
array.SelectArrayParent,
array.SelectAllArrayObjects,
array.Input3DCursorXArray,
array.Input3DCursorYArray,
array.Input3DCursorZArray,
product.AddConstrTypeInstance,
product.AddDefaultType,
product.AddEmptyType,
product.AlignProduct,
product.ChangeTypePage,
product.DisableAddType,
product.EnableAddType,
product.LoadTypeThumbnails,
product.MirrorElements,
workspace.Hotkey,
@@ -99,6 +106,8 @@ classes = (
space.GenerateSpace,
space.GenerateSpacesFromWalls,
space.ToggleSpaceVisibility,
mep.FitFlowSegments,
mep.RegenerateDistributionElement,
prop.BIMModelProperties,
prop.BIMArrayProperties,
prop.BIMStairProperties,
@@ -167,6 +176,8 @@ classes = (
roof.EnableEditingRoofPath,
roof.RemoveRoof,
roof.SetGableRoofEdgeAngle,
mep.MEPAddObstruction,
mep.MEPAddTransition,
)
addon_keymaps = []
@@ -174,7 +185,15 @@ addon_keymaps = []
def register():
if not bpy.app.background:
bpy.utils.register_tool(workspace.BimTool, after={"builtin.scale_cage"}, separator=True, group=True)
bpy.utils.register_tool(workspace.WallTool, after={"builtin.transform"}, separator=True, group=False)
bpy.utils.register_tool(workspace.SlabTool, after={"bim.wall_tool"}, separator=False, group=False)
bpy.utils.register_tool(workspace.DoorTool, after={"bim.slab_tool"}, separator=False, group=False)
bpy.utils.register_tool(workspace.WindowTool, after={"bim.door_tool"}, separator=False, group=False)
bpy.utils.register_tool(workspace.ColumnTool, after={"bim.window_tool"}, separator=False, group=False)
bpy.utils.register_tool(workspace.BeamTool, after={"bim.column_tool"}, separator=False, group=False)
bpy.utils.register_tool(workspace.DuctTool, after={"bim.beam_tool"}, separator=False, group=False)
bpy.utils.register_tool(workspace.PipeTool, after={"bim.duct_tool"}, separator=False, group=False)
bpy.utils.register_tool(workspace.BimTool, after={"bim.pipe_tool"}, separator=False, group=False)
bpy.types.Scene.BIMModelProperties = bpy.props.PointerProperty(type=prop.BIMModelProperties)
bpy.types.Object.BIMArrayProperties = bpy.props.PointerProperty(type=prop.BIMArrayProperties)
bpy.types.Object.BIMStairProperties = bpy.props.PointerProperty(type=prop.BIMStairProperties)
@@ -201,6 +220,14 @@ def register():
def unregister():
if not bpy.app.background:
bpy.utils.unregister_tool(workspace.WallTool)
bpy.utils.unregister_tool(workspace.SlabTool)
bpy.utils.unregister_tool(workspace.DoorTool)
bpy.utils.unregister_tool(workspace.WindowTool)
bpy.utils.unregister_tool(workspace.ColumnTool)
bpy.utils.unregister_tool(workspace.BeamTool)
bpy.utils.unregister_tool(workspace.DuctTool)
bpy.utils.unregister_tool(workspace.PipeTool)
bpy.utils.unregister_tool(workspace.BimTool)
del bpy.types.Scene.BIMModelProperties
del bpy.types.Object.BIMArrayProperties
@@ -134,6 +134,24 @@ class EditArray(bpy.types.Operator, tool.Ifc.Operator):
pset = tool.Ifc.get().by_id(pset["id"])
data = json.dumps(data)
ifcopenshell.api.run("pset.edit_pset", tool.Ifc.get(), pset=pset, properties={"Data": data})
tool.Blender.Modifier.Array.set_children_lock_state(element, self.item, True)
tool.Blender.Modifier.Array.constrain_children_to_parent(element)
return {"FINISHED"}
class ApplyArray(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.apply_array"
bl_label = "Apply Array"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Apply the array and keep children as separate entities. Only available for the last array"
def _execute(self, context):
obj = context.active_object
element = tool.Ifc.get_entity(obj)
pset = ifcopenshell.util.element.get_pset(element, "BBIM_Array")
data = json.loads(pset["Data"])
bpy.ops.bim.remove_array(item=len(data) - 1, keep_objs=True)
return {"FINISHED"}
@@ -142,6 +160,7 @@ class RemoveArray(bpy.types.Operator, tool.Ifc.Operator):
bl_label = "Remove Array"
bl_options = {"REGISTER", "UNDO"}
item: bpy.props.IntProperty()
keep_objs: bpy.props.BoolProperty(name="Keep Objects", default=False)
def _execute(self, context):
obj = context.active_object
@@ -152,6 +171,12 @@ class RemoveArray(bpy.types.Operator, tool.Ifc.Operator):
data = json.loads(pset["Data"])
data[self.item]["count"] = 1
if (self.keep_objs) & (self.item < (len(data) - 1)):
self.report(
{"INFO"}, "Keeping the objects is only allowed when you are removing the last Array of the object"
)
return {"FINISHED"}
props.is_editing = -1
try:
@@ -159,7 +184,11 @@ class RemoveArray(bpy.types.Operator, tool.Ifc.Operator):
except:
return {"FINISHED"}
tool.Model.regenerate_array(parent, data)
if self.keep_objs:
tool.Blender.Modifier.Array.bake_children_transform(element, self.item)
tool.Blender.Modifier.Array.set_children_lock_state(element, self.item, False)
tool.Model.regenerate_array(parent, data, self.keep_objs)
pset = tool.Ifc.get().by_id(pset["id"])
if len(data) == 1:
@@ -168,6 +197,7 @@ class RemoveArray(bpy.types.Operator, tool.Ifc.Operator):
del data[self.item]
data = json.dumps(data)
ifcopenshell.api.run("pset.edit_pset", tool.Ifc.get(), pset=pset, properties={"Data": data})
tool.Blender.Modifier.Array.constrain_children_to_parent(element)
return {"FINISHED"}
@@ -176,17 +206,35 @@ class SelectArrayParent(bpy.types.Operator):
bl_idname = "bim.select_array_parent"
bl_label = "Select Array Parent"
bl_options = {"REGISTER", "UNDO"}
parent: bpy.props.StringProperty()
parent: bpy.props.StringProperty(description="Parent Element GUID")
def execute(self, context):
try:
element = tool.Ifc.get().by_guid(self.parent)
except:
return {"FINISHED"}
self.report({"ERROR"}, f"Couldn't find array parent by guid '{self.parent}'")
return {"CANCELLED"}
obj = tool.Ifc.get_object(element)
if obj:
context.view_layer.objects.active = obj
obj.select_set(True)
tool.Blender.select_and_activate_single_object(context, active_object=obj)
return {"FINISHED"}
class SelectAllArrayObjects(bpy.types.Operator):
bl_idname = "bim.select_all_array_objects"
bl_label = "Select All Array Objects"
bl_options = {"REGISTER", "UNDO"}
parent: bpy.props.StringProperty(description="Parent Element GUID")
def execute(self, context):
try:
parent_element = tool.Ifc.get().by_guid(self.parent)
except RuntimeError:
self.report({"ERROR"}, f"Couldn't find array parent by guid '{self.parent}'")
return {"CANCELLED"}
array_objects = tool.Blender.Modifier.Array.get_all_objects(parent_element)
tool.Blender.set_objects_selection(context, active_object=array_objects[0], selected_objects=array_objects)
return {"FINISHED"}
@@ -200,7 +248,7 @@ class Input3DCursorXArray(bpy.types.Operator):
props = obj.BIMArrayProperties
cursor = context.scene.cursor
if props.use_local_space:
props.x = (Matrix.inverted(obj.matrix_world) @ cursor.matrix.col[3]).x
props.x = (Matrix.inverted(obj.matrix_world) @ cursor.matrix.translation).x
else:
props.x = cursor.location.x - obj.location.x
return {"FINISHED"}
@@ -216,7 +264,7 @@ class Input3DCursorYArray(bpy.types.Operator):
props = obj.BIMArrayProperties
cursor = context.scene.cursor
if props.use_local_space:
props.y = (Matrix.inverted(obj.matrix_world) @ cursor.matrix.col[3]).y
props.y = (Matrix.inverted(obj.matrix_world) @ cursor.matrix.translation).y
else:
props.y = cursor.location.y - obj.location.y
return {"FINISHED"}
@@ -232,7 +280,7 @@ class Input3DCursorZArray(bpy.types.Operator):
props = obj.BIMArrayProperties
cursor = context.scene.cursor
if props.use_local_space:
props.z = (Matrix.inverted(obj.matrix_world) @ cursor.matrix.col[3]).z
props.z = (Matrix.inverted(obj.matrix_world) @ cursor.matrix.translation).z
else:
props.z = cursor.location.z - obj.location.z
return {"FINISHED"}

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