Compare commits

..

912 Commits

Author SHA1 Message Date
Andrej730 6913864850 Remove triangulation on switch_representation
Now all walls, beams etc should become be displayed with quads.
2023-08-09 16:37:48 +05: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
Andrej730 12c10f98a3 Fixing bug after 28cc168
Was causing some tests to fail
2023-06-26 10:33:07 +05:00
Andrej730 e3a7f76276 Avoid ui errors after 2dc9dc200 2023-06-24 10:19:35 +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
Andrej730 6dab4501ad Unhide in render for not excluded objects #3338
Objects were hidden in render but never unhidden if they were no more in drawing's .Exlcude
2023-06-23 18:19:33 +05:00
Andrej730 2dc9dc2000 Generating IFC compatible shader graphs
Added new panel. in shader graph N-panel that allows you to create fully GLTF and IFC compatible shader graphs by just tweaking parameters from the panel. Note that it's still saved to IFC only when you hit "Save Current Style", before that it's still just stored in blender.

https://imgur.com/a/vmkoLPH

The premise is that if you want to have non-external style you'd be able to create it entirely from this panel - so there will no need to guess or to remember which parameters from the graph are supported by IFC and which are not.

It's still work in progress, still need to polish it, add support for more reflectance methods and add suport for textures (need to be able to import/export it with .ifc and set them from this panel).
2023-06-23 18:04:32 +05:00
Andrej730 addce9f0ea Fixed #3342 2023-06-23 17:15:10 +05:00
Andrej730 4a368938c3 Set shading flat by default to solve #3336
Before blender 3.6 it was setting flat shading by default, so we just keeping the same behaviour.
2023-06-22 22:00:42 +05:00
Thomas Krijnen a0f68baefc #2805 syntax 2023-06-22 16:23:32 +02:00
Thomas Krijnen 3decb48ed0 #2805 template keyword for gcc 2023-06-22 14:30:11 +02:00
Thomas Krijnen 2d330f4c90 #2805 IFC4X3 codegen 2023-06-22 14:10:55 +02:00
Thomas Krijnen 1c996013b6 #2805 Further fixes for aggregates of select 2023-06-22 14:10:55 +02:00
Thomas Krijnen 65cb47e2d8 #2805 IfcGeom dynamic casting updates 2023-06-22 14:07:34 +02:00
Thomas Krijnen e9d0b8ab66 #2805 IfcGeom updates 2023-06-22 12:07:10 +02:00
Thomas Krijnen 0bb9bcd9fb #2805 Pasta errors 2023-06-22 11:03:06 +02:00
Thomas Krijnen 997ebe48df #2805 template keyword for gcc 2023-06-22 10:48:19 +02:00
Thomas Krijnen 0cdf72fde1 #2805 Rerun codegen 2023-06-22 10:30:57 +02:00
Thomas Krijnen 97c6b99d57 #2805 Use dynamic cast for generalize() due to virtual inheritance of selects 2023-06-22 10:30:42 +02:00
Thomas Krijnen ed5d28de54 #2805 update hierarchy helper 2023-06-22 10:08:58 +02:00
Thomas Krijnen f5c102402f #2805 rerun codegen 2023-06-22 10:07:27 +02:00
Thomas Krijnen 7aa04611a1 #2805 provide aggregate typedef for select types 2023-06-22 10:07:26 +02:00
Andrej730 5c7f14cc3c option to keep the viewport position on activating drawing #3333
currently it works with alt clicking activate drawing button
https://i.imgur.com/lgvP4QA.png
2023-06-22 10:15:39 +05:00
Andrej730 9e1249d8ee Fixed #3335 caused by 36b746d 2023-06-22 09:33:40 +05:00
Bruno Postle 6cb7234cb5 BBIM fix Git panel polling repo on refresh (#3096)
Just scrolling Git panel shouldn't do any Git IO
2023-06-21 21:42:50 +01:00
Thomas Krijnen aab6a21bf3 #2805 rerun codegen 2023-06-21 19:25:05 +02:00
Thomas Krijnen c72f960ed4 #2805 statically typed select types as aggregates 2023-06-21 19:25:04 +02:00
Massimo Fabbro b63b4ce326 Add toggle space visibiity button into spatial tool menu 2023-06-21 18:57:41 +02:00
Andrej730 71113b56bf refactor with SKIP_SAVE option on operator props
Co-Authored-By: Gorgious56 <25156105+Gorgious56@users.noreply.github.com>
2023-06-21 17:49:16 +05:00
Andrej730 ef57c209a0 More options to save .ifc as relative path in blender 2023-06-21 17:39:57 +05:00
Andrej730 e99c154cb2 Fixed tests after a4a6dce 2023-06-21 14:19:26 +05:00
Andrej730 520860bdb9 Fixed test after b035129 2023-06-21 14:19:25 +05:00
Andrej730 103e8a545b Fixed test after c8c0c9a
Taking into account that now we have error messages if user will try to remove the last material layer which would make layer set invalid ifc.

FAILED test/bim/test_feature.py::test_remove_material_set_layer - AssertionError: Failed to run operator bpy.ops.bim.remove_layer(layer=IfcStore.get_file().by_id(93).MaterialLayers[...
2023-06-21 14:19:25 +05:00
Andrej730 d35c5c433f Fixed missing ifc file in api call after 28cc168
Some bim test was failing because of it

FAILED test/bim/test_feature.py::test_assign_material__material_profile_set - AssertionError: No material Default found in profiled materials: []
2023-06-21 14:19:25 +05:00
Andrej730 7dc98612e4 Fixed void tests after d3d2c36 2023-06-21 11:04:22 +05:00
Andrej730 3853258b27 Fixing test after 1c12a98
FAILED test/bim/test_feature.py::test_export_ifc__with_basic_contents_and_saving_as_a_relative_path - AssertionError: Value is C:\IfcOpenShell\src\blenderbim\test\files\temp\export.ifc
2023-06-21 10:16:03 +05:00
Andrej730 6825d26700 Small fix related to 5c2ae98c0 and a bit more descriptive operator error
FAILED test/bim/test_feature.py::test_create_project__ifc2x3 - AssertionError: Failed to run operator bpy.ops.bim.create_project
2023-06-21 09:58:41 +05:00
Andrej730 900c35e9fe Fixing collections tests related to 99d9c0f5b
FAILED test/bim/test_feature.py::test_remove_drawing - AssertionError: The collection "IfcGroup/PLAN_VIEW" does not exist
2023-06-21 09:46:44 +05:00
Bruno Postle 7aafa87b52 BlenderBIM push Git tags to remote
Shows error message if push fails
2023-06-20 16:58:55 +01:00
Andrej730 e719055d9a Fixed duplicated objects collections issue after 586707dd2
Duplicated objects were not able to have their own collection after 586707dd2

Also fixed bim test
FAILED test/bim/test_feature.py::test_copy_a_storey - AssertionError: assert 'IfcBuildingStorey/My Storey.001' in ['IfcBuildingStorey/My Storey']
2023-06-20 19:42:31 +05:00
Andrej730 a956038bfc Preserving original name on unlinking
Unlinking was using copied object name for the unlinked object instead of the original one.
So it worked like Cube -> IfcWall/Cube -> Cube.001 and bim test was failing:
FAILED test/bim/test_feature.py::test_unlink_object - AssertionError: The object "Cube" does not exist
2023-06-20 19:18:40 +05:00
Andrej730 5c2ae98c09 Fixed couple bugs in test_feature
1) Explicitly set empty ifc project not to load demo library - was still loading because it was using my default scene which caused errors.
2) typo in dimension check assertion message
2023-06-20 19:18:39 +05:00
Andrej730 99d9c0f5bf Fixing collectors tests after 1f43ec8 2023-06-20 17:50:30 +05:00
Andrej730 2fbb9c569b Fixing tool tests 2023-06-20 17:30:01 +05:00
Andrej730 4a9d1ff038 Fixing core tests 2023-06-20 16:31:10 +05: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
Andrej730 6cd9161986 Fixed import error with filtering out IfcGrid's spatial element #3283
Fixed error that was occuring if you try to import .ifc file where spatial element contains only grid and will use advanced mode, filter mode = "IFC Class" and check "Filter Spatial Elements".

Error occured because spatial element was filtered out but then when ifc grid was created and importer to find a spatial element based collection for the grid it couldn't find it.

Traceback:
Error: Python: Traceback (most recent call last):
  File "\blenderbim\bim\module\project\operator.py", line 595, in execute
    ifc_importer.execute()
  File "\blenderbim\bim\import_ifc.py", line 260, in execute
    self.place_objects_in_collections()
  File "\blenderbim\bim\import_ifc.py", line 1494, in place_objects_in_collections
    self.place_object_in_collection(self.file.by_id(ifc_definition_id), obj)
  File "\blenderbim\bim\import_ifc.py", line 1498, in place_object_in_collection
    self.place_object_in_decomposition_collection(element, obj)
  File "\blenderbim\bim\import_ifc.py", line 1517, in place_object_in_decomposition_collection
    return self.place_object_in_spatial_decomposition_collection(element, obj)
  File "\blenderbim\bim\import_ifc.py", line 1545, in place_object_in_spatial_decomposition_collection
    self.collections[container.GlobalId].children.link(grid_collection)
KeyError: '33AiKftFj2aAf2MqQr5Gs7'
2023-06-19 19:04:51 +05:00
Andrej730 982f4a4965 Fixed getting filtered spatial elements at import for ifc2x3 #3283
In ifc2x3 IfcProject is not IfcContext because there is no IfcContext in ifc2x3 and it was causing the error below on .ifc import with filtered spatial elements.
Also added small refactor

Traceback:
Error: Python: Traceback (most recent call last):
  File "\blenderbim\bim\module\project\operator.py", line 595, in execute
    ifc_importer.execute()
  File "\blenderbim\bim\import_ifc.py", line 253, in execute
    self.create_spatial_elements()
  File "\blenderbim\bim\import_ifc.py", line 688, in create_spatial_elements
    self.create_generic_elements(self.spatial_elements)
  File "\blenderbim\bim\import_ifc.py", line 698, in create_generic_elements
    products = self.create_products(elements)
  File "\blenderbim\bim\import_ifc.py", line 717, in create_products
    iterator = ifcopenshell.geom.iterator(settings, self.file, multiprocessing.cpu_count(), include=products)
  File "\blenderbim\libs\site\packages\ifcopenshell\geom\main.py", line 99, in __init__
    raise ValueError("include and exclude need to be an aggregate of IfcProduct?")
ValueError: include and exclude need to be an aggregate of IfcProduct?
2023-06-19 19:03:18 +05:00
Thomas Krijnen c5aab16696 Remove dependency on bootstrap 2023-06-19 14:34:14 +02:00
Thomas Krijnen 6e1b18ba33 #3120 item_id vector test case 2023-06-19 13:48:35 +02:00
Thomas Krijnen 2b3d547c99 #3120 item_id vector 2023-06-19 13:47:55 +02:00
Andrej730 a34be6ae5d Getting shader graph nodes by type rather than name #3316
Because relying on names doesn't work for non english users
2023-06-19 16:17:37 +05:00
Andrej730 dfc5a280a4 Fixed error on disabling editing window 2023-06-19 15:51:01 +05:00
Andrej730 6a2bbfa85c Added button to update current style #3293
Example - https://i.imgur.com/f4Yi3MR.png
Now changing style type should be a bit more intuitive - previously there were no button and it wasn't possible to change style type for all selected objects / changing style type for the entire scene were hidden in N-panel Miscellaneous section.

Now it also able to change style type for all blender materials, even the ones that don't have any objects using them (wasn't possible before 7c0c934b4)
2023-06-19 11:02:42 +05:00
Bruno Postle ddef4820d0 BlenderBIM Git merge asymmetry heuristic (#3096)
`ifcmerge` is asymmetrical. When both branches have new entities, either
local or remote step Ids need to be renumbered to resolve the merge
conflict.

If the remote branch is 'origin/main' or 'main' we preserve remote Ids,
otherwise we preserve local Ids.

This should support a workflow where a local 'main' branch is kept
pristine, but other local branches are treated as disposable; it should
also support a workflow where multiple authors access a shared 'origin'.
2023-06-16 19:42:57 +01:00
Thomas Krijnen e6b05b547b Further defer sql.py imports for ifcopenbot to succeed 2023-06-16 20:32:40 +02:00
Thomas Krijnen 5e8ec48d98 Comment out import mysql.connector 2023-06-16 14:16:17 +02:00
Thomas Krijnen 9c21304629 Correct if-clause 2023-06-16 14:08:48 +02:00
Thomas Krijnen 1278f98df6 Add test 2023-06-16 14:08:48 +02:00
Totally a booplicate d31b470e83 smol fix 2023-06-16 14:08:48 +02:00
Totally a booplicate 1ecb25684e fix missing else clause 2023-06-16 14:08:48 +02:00
Totally a booplicate 5999976d37 raise TypeError on invalid pset 2023-06-16 14:08:48 +02:00
Totally a booplicate 51e5f843ba rework unpack_unit_value
handle the `None` case, simplify logic
2023-06-16 14:08:48 +02:00
Totally a booplicate 3d787338fb improve naming of the new method 2023-06-16 14:08:48 +02:00
Totally a booplicate de67648f3e improve update_existing_properties
explicit class check
error out on a not supported type
better methods names
2023-06-16 14:08:48 +02:00
Totally a booplicate afa803ea62 implement _try_purge
per review comment about repeating code
2023-06-16 14:08:48 +02:00
Totally a booplicate 3a0aaee03b restructure if/elif/else chain 2023-06-16 14:08:48 +02:00
Totally a booplicate a5353e02b5 support tuples 2023-06-16 14:08:48 +02:00
Totally a booplicate 1acc743370 move prop check 2023-06-16 14:08:48 +02:00
Totally a booplicate 375f4f2a86 per review
change exception message
remove TODO
pass `Unit`
raise `NotImplementedError` when no template was found
2023-06-16 14:08:48 +02:00
Totally a booplicate d4b88fd422 resolve TODO
This is being fixed, we should always have a class to use
2023-06-16 14:08:48 +02:00
Totally a booplicate df6cc74a87 attempt to improve pset support
1. Adds support to directly passed in property entities
2. Adds support for tuples
3. Adds support for P_LISTVALUE
2023-06-16 14:08:48 +02:00
Balázs Dukai 80846061f2 Don't use importlib for version 2023-06-16 13:29:24 +02:00
Balázs Dukai 80f9590410 Get package version from pyproject.toml 2023-06-16 13:29:24 +02:00
Balázs Dukai 16cda28d16 Include GenericCityObject extension 2023-06-16 13:29:24 +02:00
Balázs Dukai 24ea024a0d Add parameters 2023-06-16 13:29:24 +02:00
Balázs Dukai e90d7ccdb1 Init package 2023-06-16 13:29:24 +02:00
Vasile-Corjan 1a3bf513a7 Update README.md 2023-06-16 08:34:05 +02:00
Vasile-Corjan a855c40426 Update README.md
I have added the parameters "minOccurs" and "maxOccurs"
2023-06-16 08:34:05 +02:00
Dion Moult c8ede38edb Run black on existing entity_instance 2023-06-16 16:17:17 +10:00
Dion Moult 704e27ecca Build testing prototype of an SQLite based file / entity_instance. 2023-06-16 16:16:22 +10:00
Dion Moult 438ddc17f5 Document two answers to frequently reported bugs: missing numpy and blend / ifc out of sync. 2023-06-16 14:13:15 +10:00
Dion Moult a8dfed50e4 Minor geometry processing documentation fix 2023-06-16 13:32:33 +10:00
Dion Moult 8cb99a77cf Convert Ifc2Sql script to IfcPatch recipe and add geometry support 2023-06-16 13:32:33 +10:00
Dion Moult f9b6e6f098 Fetch pset and qto names via pset data instead of real time to prevent UI lag on larger models. 2023-06-16 13:32:33 +10:00
Dion Moult 1365b76149 Lazily check for BBIM_Railing psets to prevent unnecessarily fetching psets for every element when loading models. 2023-06-16 13:32:33 +10:00
Dion Moult 47cc3158fd IfcPatch can now support patches that write to arbitrary files or have no output. 2023-06-16 13:32:33 +10:00
Dion Moult bac683e105 Fix bug for getting psets of materials and profiles in IFC2X3 2023-06-16 13:32:33 +10:00
Thomas Krijnen 840818d681 #3308 add assert 2023-06-15 21:27:42 +02:00
Thomas Krijnen 206933a2f2 #2819 Handle periodic bspline curves in serialization 2023-06-15 16:19:05 +02:00
Thomas Krijnen ff209b4c11 #2819 Mark rep id as AdvancedBrep 2023-06-15 16:18:29 +02:00
Sigma Dimensions 4bc987924d enable activating object type when trying to select type from an object ( whilst the type is hidden from view layer) 2023-06-14 13:11:17 +01:00
Massimo Fabbro f26cfc93ea Now generate spaces from walls considers also columns in order to create the contours 2023-06-14 12:58:39 +02:00
Thomas Krijnen 4bbb6cd2df #3305 Less pessimistic tolerance comparison in newells method 2023-06-14 11:47:24 +02:00
Thomas Krijnen 9411a1d94b #3305 one more iteration on lower fuzziness level for boolean ops 2023-06-14 11:47:24 +02:00
Sigma Dimensions 6a287ced7c temporarily hide IFC Spatial Manager for IFC2X3 models 2023-06-14 10:47:00 +01:00
Sigma Dimensions fb02ac34d4 Enable selecting group elements 2023-06-14 09:35:54 +01:00
Sigma Dimensions 81fd84223a Enable selecting a container's children from the IFC Spatial Manager panel 2023-06-13 21:17:02 +01:00
Sigma Dimensions a8f1a28ac2 fix replacing material with material profile 2023-06-13 14:04:28 +01:00
Sigma Dimensions d2f4f0d8c2 experimental patch of non parametric mep segments 2023-06-13 13:26:53 +01:00
Sigma Dimensions 1c17cccc45 fix missing import 2023-06-13 10:34:21 +01:00
Sigma Dimensions 061930bc8e Move Spatial Manager UI to "IFC Project Set-up" 2023-06-13 10:32:25 +01:00
Sigma Dimensions 28cc168b3b split material assign/unassign operators into tool and core 2023-06-13 10:28:31 +01:00
Sigma Dimensions 88f53f7931 fix delete material operator 2023-06-12 15:24:51 +01:00
Sigma Dimensions 755fa81817 Fix displaying connections for selected distribution ports 2023-06-12 15:23:24 +01:00
Sigma Dimensions 36fab12847 Refactor and re-use common spatial_tool.select_products 2023-06-12 15:21:33 +01:00
Andrej730 32393136d2 Support external shaders from currently opened blend file
Previously it was crashing if you try use shader from current .blend file as external.
I guess we now kind of support external internal shaders 🙃
2023-06-12 17:28:52 +05:00
Andrej730 c7b3331496 switch to external styles for rendered shading in viewport
Unfortunately I couldn't find a way to make it work with areas that user created during current blender session. But new areas will have this feature too after blender restart
2023-06-12 17:28:52 +05:00
Andrej730 7c0c934b4f prioritize external styles on ifc import
also found a way to switch shade graph for two materials without juggling active materials and active objects - it was necessary to make it work on import without adding temporary active object
2023-06-12 17:28:51 +05:00
johanrd 62944e04e9 fix typo in README.md 2023-06-12 11:22:31 +02:00
Bruno Postle 3a93b15b55 BBIM show all fetched remote Git branches (#3096) 2023-06-11 20:49:55 +01:00
Gorgious 83cd00271b Fix #3142 : Use Tab hotkey to edit horizontally layered objects 2023-06-11 18:17:03 +02:00
Ahmad Saleem Z 820686a39b added link to aws/lambda guide in installation guide 2023-06-11 18:04:34 +02:00
Ahmad Saleem Z 4fe896a3b6 fix ARG in Dockerfile 2023-06-11 18:04:34 +02:00
Ahmad Saleem Z 975b1c444c documentation 2023-06-11 18:04:34 +02:00
Ahmad Saleem Z c3f2046491 example fixes 2023-06-11 18:04:34 +02:00
Ahmad Saleem Z 3a1557bedc added aws/lambda sample 2023-06-11 18:04:34 +02:00
Bruno Postle a71b5e1f11 BlenderBIM operator to add remote repo (#3096)
Procedure to merge a pull request would be: Add Remote, select the added
remote in the pull-down, Fetch from remote, select the added remote
branch in the pull-down, Merge, delete remote
2023-06-10 22:04:01 +01:00
Dion Moult 4806b9a260 Toggle between full schema (i.e. all tables for every declaration) and partial schema dump into SQL 2023-06-10 23:14:32 +10:00
Bruno Postle 0d67c43e16 BlenderBIM operator to delete a Git remote (#3096) 2023-06-10 11:08:49 +01:00
Bruno Postle e2b52e7ed9 BlenderBIM fix warning writing .git/config (#3096) 2023-06-10 10:07:17 +01:00
Balázs Dukai 3c53067567 Add copyright 2023-06-10 15:39:49 +10:00
Balázs Dukai 5ad7d4bb2a Update to ifcopenshell 0.7.0 and cjio 0.8
LoD is string in v1.1
2023-06-10 15:39:49 +10:00
Balázs Dukai 3803942c5f Update to v1.1 CityObject types 2023-06-10 15:39:49 +10:00
Andrej730 678f50f17d IfcGitData update to ifcgit operators poll() methods
Added loading IfcGitData to ifcgit operator's poll() methods because it was getting a bit spammy in console if you don't use git.

Traceback (most recent call last):
  File "\scripts\addons\blenderbim\bim\module\ifcgit\operator.py", line 255, in poll
    if IfcGitData.data["ifcmerge_exe"]:
KeyError: 'ifcmerge_exe'
Traceback (most recent call last):
  File "\scripts\addons\blenderbim\bim\module\ifcgit\operator.py", line 147, in poll
    repo = IfcGitData.data["repo"]
KeyError: 'repo'
Traceback (most recent call last):
  File "\scripts\addons\blenderbim\bim\module\ifcgit\operator.py", line 18, in poll
    path_ifc = IfcGitData.data["path_ifc"]
KeyError: 'path_ifc'
Traceback (most recent call last):
  File "\scripts\addons\blenderbim\bim\module\ifcgit\operator.py", line 45, in poll
    path_ifc = IfcGitData.data["path_ifc"]
KeyError: 'path_ifc'
Traceback (most recent call last):
  File "\scripts\addons\blenderbim\bim\module\ifcgit\operator.py", line 190, in poll
    repo = IfcGitData.data["repo"]
KeyError: 'repo'
Traceback (most recent call last):
  File "\scripts\addons\blenderbim\bim\module\ifcgit\operator.py", line 111, in poll
    repo = IfcGitData.data["repo"]
KeyError: 'repo'
2023-06-09 19:38:16 +05:00
Andrej730 04fe2bdf2e External Styles
Added support for external blender styles that will be saved to ifc.
What it basically means that you can append material from external .blend file as a style that can be loaded in your blender material anytime.

1) External style can be added in Material Tab
https://i.imgur.com/BYfesi5.png

Note that you select material name at the right side of file selecting panel:
https://i.imgur.com/mvqaaod.png

2) After adding external style you can switch between external and other surface styles:
https://i.imgur.com/YtCz8an.png

3) There is also option to switch styles to External for all objects in the scene:
https://i.imgur.com/u3zTcX9.png

Some other changes:
- some ui adjustments
- moved functions that create blender shaders to tool.Loader (they also reset shader graph now so it's possible to use them on non-default blender materials)
- some new tools for copying node graph from one material to another, appending data blocks
- new operator for browsing external styles can be reused later to append other data blocks
2023-06-09 18:40:53 +05:00
Dion Moult ed990c3e83 Experimental support for storage of IFC in MySQL and SQLite 2023-06-09 22:16:54 +10:00
D4ve-R 741c78cd6c Revert "build scripts"
This reverts commit 6c1218d352.
2023-06-09 13:28:25 +02:00
D4ve-R 2b88c542aa build scripts 2023-06-09 13:28:25 +02:00
D4ve-R 1f6b55e639 prepare for referencing 2023-06-09 13:28:25 +02:00
D4ve-R 9f3fb13cde metersPerUnit 2023-06-09 13:28:25 +02:00
D4ve-R 42f1d9c160 usd rotateXYZop 2023-06-09 13:28:25 +02:00
D4ve-R f0875835c1 Update submodule 2023-06-09 13:28:25 +02:00
D4ve-R 0828a89b66 fix typo 2023-06-09 13:28:25 +02:00
D4ve-R a8ca66b425 refactor 2023-06-09 13:28:25 +02:00
D4ve-R 92b03c6b59 stage up axis 2023-06-09 13:28:25 +02:00
D4ve-R d316efe902 fix filename bug 2023-06-09 13:28:25 +02:00
D4ve-R 77e4e939ce filename bug 2023-06-09 13:28:25 +02:00
D4ve-R fe7504edf2 cmake warnings 2023-06-09 13:28:25 +02:00
D4ve-R a8f1ab1923 material subsets 2023-06-09 13:28:25 +02:00
D4ve-R 1b5f9497d4 works fine 2023-06-09 13:28:25 +02:00
D4ve-R a7bbb35c5e compile & link successful 2023-06-09 13:28:25 +02:00
D4ve-R 1787f064f6 materials 2023-06-09 13:28:25 +02:00
D4ve-R d1c24ebba4 Add USDSerializer to IfcConvert 2023-06-09 13:28:25 +02:00
D4ve-R cc6dacb286 Add USDSerializer write-only 2023-06-09 13:28:25 +02:00
Thomas Krijnen c67eb60fd5 #2942 Escape SVG text labels 2023-06-09 11:34:50 +02:00
Sigma Dimensions 59c482489f Merge pull request #3279 from nelsonhp3/v0.7.0
implements fileselect to export_cost_schedules
2023-06-08 23:48:30 +01:00
Andrej730 7a0963617e Fixed small error on disabling editing extrusion axis 2023-06-08 17:58:06 +05:00
Chetan e4ec1dc457 If an IFC file has an IFCRELASSOCIATESMATERIAL but did not supply an IFCMATERIAL, IfcConvert will crash. This commit prevents the crash, catches the exception and prints an error on the terminal 2023-06-08 10:01:24 +02:00
Dion Moult b035129788 Fix bug where you couldn't unassign inherited materials. 2023-06-08 16:00:31 +10:00
Dion Moult 78be8114ac Fix sync bug where unused styles would be purged when reloading a blender file. 2023-06-08 16:00:31 +10:00
Nelson Henrique 18ad025c5d implements fileselect to export_cost_schedules 2023-06-08 00:24:48 +00:00
Martin15135215 5825e0c71b CITATION.cff for BIMTester 2023-06-08 09:45:54 +10:00
Martin15135215 df64ca4cfb CITATION.cff for ifc5d 2023-06-08 09:45:54 +10:00
Martin15135215 fc1f5496a0 CITATION.cff for ifc4d 2023-06-08 09:45:54 +10:00
Martin15135215 5a9dbc5a36 CITATION.cff for ifc2ca 2023-06-08 09:45:54 +10:00
Martin15135215 eaeeede287 CITATION.cff for bsdd 2023-06-08 09:45:54 +10:00
Martin15135215 e78b3a3977 CITATION.cff for bcf 2023-06-08 09:45:54 +10:00
Bruno Postle a8ca8217ea BlenderBIM fix Git error on simple merge (#3096)
Running git commit after a simple merge (that hasn't required conflict
resolution) is a noop, but generates an annoying message anyway.
2023-06-07 22:50:20 +01:00
Dion Moult 77c2e30733 Fix #3272. See #3069. Fix incorrect refactoring when I was decoupling collections. 2023-06-07 22:13:38 +10:00
Gorgious56 5a3a387b8c Fix error when going out of edit mode with TAB when editing an arbitrary profile 2023-06-07 11:22:06 +02:00
Gorgious56 f68f60832c Fix profile based geometry not re-generating properly when exiting edit mode and the profile geometry wasn't modified 2023-06-07 11:21:43 +02:00
Martin15135215 b05417a4d8 Update author in CITATION.cff 2023-06-07 10:54:47 +02:00
Martin15135215 c2a4e30fae CITATION.cff file for BlenderBIM 2023-06-07 10:54:47 +02:00
Thomas Krijnen 1a0280928b #3201 take into account edge orientation correctly 2023-06-07 08:43:08 +02:00
Andrej730 dda16c847d ids/classes namespaces for drawings in the sheet #3047
Now we add some prefix based on drawing id to each id in the drawing and also add this prefix as a class and add it to all css selectors.
Reason for that so each drawing on the sheet can have it's own set of styles that won't be affecting other drawings.
Example - https://i.imgur.com/kqRCwJW.png
2023-06-07 11:07:48 +05:00
Dion Moult 47c6c2ce9a See #3266. Write documentation on how to use IfcCSV. 2023-06-07 16:00:12 +10:00
Dion Moult 3758c454c9 Add support for Pandas DataFrames in IfcCSV and clean up function signatures 2023-06-07 15:59:20 +10:00
Dion Moult 4b6424e0e1 Fix #3264. Describe IFC to JSON conversion (and other formats) in documentation. 2023-06-07 14:30:48 +10:00
Bruno Postle d71887d846 BlenderBIM fix Git merge log history (#3096)
The GitPython commit() method isn't suitable for completing a merge as
it only sets one parent, losing all history from the merged branch.
2023-06-07 00:45:01 +01:00
Thomas Krijnen d61945a575 #3255 unreferened param 2023-06-06 13:25:13 +02:00
Gorgious56 45e6ae6708 Revert unselect non-ifc object using TAB 2023-06-06 11:59:05 +02:00
Gorgious56 ebbf30a88c Fix wall mounted handrail railing modifier not regenerating properly when going out of edit mode with the tab key if the path wasn't modified 2023-06-06 09:57:00 +02:00
Dion Moult e5769766af Fix #3226. There is now a faster deletion mode that kicks in for large models and large geometry deletions. 2023-06-06 17:40:31 +10:00
Dion Moult 64b9d2e5e3 See #3226. New batched remove_deep2() which uses serialisation for faster removal performance on large graphs. 2023-06-06 17:40:31 +10:00
Andrej730 3213ffe760 Fixed #3259 2023-06-06 10:44:27 +05:00
Bruno Postle a851689bca BlenderBIM use git shortlog to summarise merge log 2023-06-05 21:22:16 +01:00
Gorgious56 98f2bd66b6 Edit profile based representations with TAB (#3142)
* You can now edit profile based representations with TAB

* Minor fix

* Minor fix

* You can now edit parametric railing paths with TAB
Still not sure about the implementation though

* use shortcut instead of direct link to railing edition

* Add utility to check if a mesh is profile based or its representation is a swept solid

* Fix bug when trying to edit an empty object

* Prevent editing door, window and stair modifiers since they're fully parameterized by the modifier properties

* You can now edit roof modifier, railing modifier and various profile based objects

* You can now edit axis profile based objects
2023-06-05 18:07:25 +02:00
Andrej730 1c12a98b68 Solved #3219 2023-06-05 18:45:02 +05:00
Andrej730 a3859385c4 Blender curves to IfcIndexedPolyCurve #3233
Now blender curves segments saved in IfcIndexedPolyCurves instead of creating individual polygons.
Also changed ShapeBuilder.polyline - now it will try to store points in IfcLineIndex where it's possible (previously it was storing just 2).
2023-06-05 17:21:27 +05:00
Andrej730 c7394faf51 Display units for more pset/qset props #3235
Now current project units displayed for area and volume properties too.
https://i.imgur.com/7dgl1pC.png
Also added support for imperial units symbols.
2023-06-05 14:54:41 +05:00
Andrej730 3907ae656c small note for unsupported pset prop types 2023-06-05 12:44:50 +05:00
Andrej730 5fc31be8aa Add to scene properties whether we use relative path to .ifc #3251
Previously it was stored as operator's prop which is (turned out) reseted on reopening .blend.
2023-06-05 12:09:59 +05:00
Andrej730 7976fb4d09 Small fix for 1f43ec870
Was getting this error at project start:

Traceback (most recent call last):
  File "\scripts\addons\blenderbim\bim\module\project\operator.py", line 332, in execute
    return IfcStore.execute_ifc_operator(self, context)
  File "\scripts\addons\blenderbim\bim\ifc.py", line 410, in execute_ifc_operator
    result = getattr(operator, "_execute")(context)
  File "\scripts\addons\blenderbim\bim\module\project\operator.py", line 345, in _execute
    self.import_type_from_ifc(element, context)
  File "\scripts\addons\blenderbim\bim\module\project\operator.py", line 374, in import_type_from_ifc
    collection_obj = collection.BIMCollectionProperties.obj
AttributeError: 'LayerCollection' object has no attribute 'BIMCollectionProperties'
2023-06-05 11:50:27 +05:00
Andrej730 01635ecd8a small fix for syntax warning after 4865b62ea
SyntaxWarning: "is not" with a literal. Did you mean "!="?
2023-06-05 11:42:28 +05:00
Andrej730 f6657113b6 Spatial/Structural Tools icons 2023-06-05 11:18:52 +05:00
Andrej730 480f1a91a6 Moving operators to Spatial Tool
Changes:
- Moved Shift A "Add boundary" from BIM Tool to Spatial Tool
- Moved Alt B "Show boundaries" from BIM Tool to Spatial Tool
2023-06-05 11:18:52 +05:00
Massimo Fabbro 4865b62ea9 Fix create spaces from walls z-value (#3248)
Co-authored-by: Dion Moult <dion@thinkmoult.com>
2023-06-05 15:17:03 +10:00
Massimo Fabbro 9a440899be New toggle space visibility button 2023-06-05 15:15:37 +10:00
Dion Moult 3ee6bdf207 You can now get decompositions non-recursively in element utilities 2023-06-05 15:11:55 +10:00
Dion Moult 1f43ec8709 Fix #3069. (Again) review all occurrences of users_collection and collection.name 2023-06-05 15:11:19 +10:00
Trashman247 d030582fbb Keep dist-info in compiling brickschema and add persistence extras 2023-06-04 20:53:23 -07:00
Bruno Postle 60a8d9b2fd Include commit summary of merged branches (#3096)
Git merges usually transfer the commit history, but when we resolve a
conflict with a 'mergetool' (ie. ifcmerge), this history is lost,
everything is squashed into a single commit. Now a summary of the
merged changes is appended to the merge message.
2023-06-04 22:47:10 +01:00
Bruno Postle a5de5d8c0e Display multi-line commit and tag messages (#3096) 2023-06-04 18:39:03 +01:00
Sigma Dimensions b506e7dbbc Add a banner to ifcopenshell README 2023-06-04 12:53:21 +01:00
Bruno Postle db8f1a9b70 Operator to delete a Git tag (#3096) 2023-06-04 11:17:45 +01:00
Bruno Postle 032f52d434 Operator to view selected object Git history #3096
A new BlenderBIM > Git History side-panel to show the raw Git log
for the step entity of the selected object
2023-06-04 09:52:30 +01:00
Sigma Dimensions 1003e8de91 Prototype Building Storey Manager #3246 2023-06-03 21:07:20 +01:00
Sigma Dimensions c6ef402b60 fix draw_project_not_saved_ui for BIM documentation 2023-06-03 11:55:44 +01:00
Andrej730 5805653431 same generate_pset_templates.py for ifcopenshell and ifc dev repos
same script will be easier to maintain
2023-06-03 12:57:48 +05:00
Andrej730 186da73135 Fixed validation errors in pset templates library
- missing project and IfcRelDeclares guids
- missing RelatingContext in IfcRelDeclares
2023-06-03 11:00:29 +05:00
Andrej730 528481e774 Fixed missing PrimaryMeasureType for P_LISTVALUE in pset templates #3236
For some reason there is also was missing PrimaryUnit in IFC2X3. But it's unrelated to the changes from this commit (generating pset templates before this commit already has PrimaryUnit there).
2023-06-03 10:34:13 +05:00
Sigma Dimensions 886ab52556 You can now review and assign currencies from the Cost Schedule Panel 2023-06-02 14:52:10 +01:00
Sigma Dimensions 8196f31d82 Display currency on cost item values and total cost 2023-06-02 14:49:59 +01:00
Andrej730 de3b844829 Placeholders for spatial and structural tools 2023-06-02 17:57:44 +05:00
Andrej730 fa4e19eba7 Tests for external svg references 2023-06-02 12:01:44 +05:00
Trashman247 1488ae40ee Update Makefile 2023-06-02 14:09:58 +10:00
Sigma Dimensions aa004ef0c6 auto refresh tasks assigned to products when filtering by active schedule 2023-06-02 00:34:08 +01:00
Sigma Dimensions 0c946fae36 fix removing cost item value 2023-06-02 00:31:50 +01:00
Sigma Dimensions f297739836 BBIM 5D: Various Cost Schedule UI Improvemements 2023-06-02 00:30:49 +01:00
Thomas Krijnen b33e58f40b fix syntax error on gcc 2023-06-01 22:03:31 +02:00
Thomas Krijnen 30d9743ae7 typename :( 2023-06-01 21:47:07 +02:00
Thomas Krijnen c974306636 #3141 eliminate_narrow_operands() 2023-06-01 21:42:31 +02:00
Thomas Krijnen 602176815a Commit obsolete fix just in case 2023-06-01 21:41:08 +02:00
Thomas Krijnen 7652144184 Fix IfcFile::getInverse<T>() 2023-06-01 21:39:08 +02:00
Andrej730 0fd94d291b References for adding external svgs to the sheets
It works exactly the same as schedules - you add some .svg file, path to it stored in ifc and you can attach it to the sheet.

UI - https://i.imgur.com/tGbCGBb.png

Still need to add some tests for references
2023-06-01 21:41:06 +05:00
Sigma Dimensions 3c9f09f246 Use util.element.get_predefined_type() #065bda6 2023-06-01 13:07:00 +01:00
Thomas Krijnen d60fbd2c09 Don't assert but just if-check for not terminating rule execution on very invalid files 2023-06-01 14:06:41 +02:00
Sigma Dimensions 9d869ebb6d Run costing tests and rename changed methods 2023-06-01 13:02:01 +01:00
Dion Moult 971e508e3d See #3069. Continued refactoring to not rely on users_collection[0]. 2023-06-01 21:18:32 +10:00
Dion Moult 7020caf682 See #3069. More cleanup moving away from collection names and assumed single collections. 2023-06-01 17:39:36 +10:00
Dion Moult 7cd7ad1281 Fix regressions after making aggregate based on selected objects. 2023-06-01 16:59:53 +10:00
Dion Moult 586707dd28 Fix #3069. WARNING: dangerous change. Objects that have their own collection now have a pointer to that collection. This fixes a variety of subtle sync bugs. 2023-06-01 16:59:53 +10:00
Andrej730 4bb46fa607 Remove test to reproduce #3169 as irrelevant 2023-06-01 10:01:14 +05:00
Andrej730 7d5beb6c80 Added test to cover 2629172ff 2023-06-01 10:01:13 +05:00
Andrej730 ecc0263062 Fixed bug with saving data to Pset_StairFlightCommon not in project units 2023-06-01 10:01:13 +05:00
Dion Moult 39c5dc6ea4 Merge pull request #3214 from maxfb87/new_patch_recipie
New patch recipe
2023-06-01 09:55:10 +10:00
Thomas Krijnen e6d6ab6da7 More informed invalid shape reporting 2023-05-31 22:15:26 +02:00
Thomas Krijnen d7c3b5a990 #3201 apply boolean results in batches as well 2023-05-31 22:15:01 +02:00
Thomas Krijnen 3409cd7c29 #3201 approximate circular sharp corners 2023-05-31 22:13:53 +02:00
Massimo Fabbro 91176349f7 Remove old patch file 2023-05-31 21:07:34 +02:00
Massimo Fabbro 6634025346 Move the recipe into the blenderbim script folder and rewrite it as a script 2023-05-31 21:02:29 +02:00
Sigma Dimensions a771061495 Exporting cost schedules opens the folder directory 2023-05-31 16:21:19 +01:00
Andrej730 2629172ff4 Displaying project units for length values in psets and qsets
Examples:
- https://i.imgur.com/ww7MJ7w.png
- https://i.imgur.com/N1rOELc.png
2023-05-31 18:16:01 +05:00
Andrej730 36b746da7a Railing, Door Modifiers - project units props
Example - https://i.imgur.com/SJOQMMm.png
2023-05-31 14:33:18 +05:00
Burak Yildiz d3aa2e87c9 fix ifcopenshell.version 2023-05-31 10:12:16 +02:00
Andrej730 c6090ef9b3 black format 2023-05-31 12:06:03 +05:00
Andrej730 6354a11645 maintaining same roof thickness for different rafter edge angles
PS roof code is not getting simpler to work with 🥲
2023-05-31 12:04:10 +05:00
Dion Moult 11ec8e04c2 Removing authoring mode which was an outdated concept. 2023-05-31 15:35:53 +10:00
Dion Moult 8a9998bc65 Fix #3163. Use text in lieu of inability to use URIReferences in drawing pset. 2023-05-31 15:14:53 +10:00
Sigma Dimensions 065bda604e Cost schedule name label now shows the predefined type 2023-05-31 04:38:21 +01:00
Dion Moult 471aeab439 Fix #3120. Don't remove doubles as it may break mesh manifoldness. 2023-05-31 13:26:53 +10:00
Dion Moult f2c757b1bb Fix #3113. Support regex in selector filter values. 2023-05-31 13:23:51 +10:00
Andrej730 f6e72ec06f rafter edge angle and roof thickness #3092
Added option to adjust rafter edge angle and roof thickness
Short demonstration - https://imgur.com/a/TsYSs6i
2023-05-30 18:03:35 +05:00
Andrej730 9dd82036ba Roof modifier - asymmetrical gable #3093
Example - https://i.imgur.com/EHaSt16.png
2023-05-30 18:03:34 +05:00
Dion Moult ff2fa70776 Close #3139. Bump IfcOpenShell. 2023-05-30 21:06:09 +10:00
Dion Moult e4b1f56f6d Fix #3211. Generated fills now are siblings to the element that they void. 2023-05-30 16:07:31 +10:00
Andrej730 0d7a789ed8 Schedule Builder - text/background color support #3154 2023-05-30 10:58:56 +05:00
Andrej730 d73e77c5ed separate ifc text literals in svg with newline #3207 2023-05-30 10:30:22 +05:00
Dion Moult dddbf64e93 Fix #3212. Bug where viewport camera bisection didn't have enough tolerance and sometimes wasn't visible. 2023-05-30 14:31:03 +10:00
Dion Moult 1d5891fde7 Fix #3209. Handle loading of custom scales, fix incomplete NTS implementation, and use Lark for more reliable imperial scale parsing. 2023-05-30 12:53:02 +10:00
Dion Moult c5de64de51 Merge pull request #3177 from IfcOpenShell/elevation_tweaks
a few plan level tweaks
2023-05-30 09:53:42 +10:00
Dion Moult a43b2a2ae8 Merge branch 'v0.7.0' into elevation_tweaks 2023-05-30 09:53:19 +10:00
Massimo Fabbro 7ab92dfa0b IfcPatch ReplaceDrawingPath recipie improved: now it's possible to use it on Linux and on Windows 2023-05-29 20:01:45 +02:00
Massimo Fabbro ff81286874 Now it's possible to fix drawing reference path problem saved in Windows and opened in Linux 2023-05-29 17:14:06 +02:00
Andrej730 51ad31f5a3 Fixed Stair annotation bug introduced in 762299124
in 762299124 I've changed number of segments and verts but never adjusted StairDecorator - so it wasn't drawing parts of the annotation
2023-05-29 17:13:51 +05:00
Andrej730 6336db0069 Optimized iteration over objects for viewport annotations #3198
This commit should increase viewport annotations performance significantly on large projects. Described in #3198
2023-05-29 16:47:47 +05:00
Dion Moult 7d7122ee4c Fix #3210. Revert "BBIM Drawings: Exclude ifc objects hidden from render view"
This reverts commit 6f39648077.
2023-05-29 20:31:23 +10:00
Dion Moult 7d852f9e51 Fix #3138. Remove manual copy class and be more intelligent about what classe can be reassigned. 2023-05-29 17:21:05 +10:00
Andrej730 605097de76 Same distance function for viewport and svg #1321 #3198 2023-05-29 12:11:29 +05:00
Dion Moult 07638dac27 Fix #3200. Bug where include / exclude filters could break the drawing. 2023-05-29 15:27:48 +10:00
Sigma Dimensions 6f39648077 BBIM Drawings: Exclude ifc objects hidden from render view 2023-05-28 14:47:47 +01:00
Sigma Dimensions 90c19f8454 Ctrl+P and Alt+P BIMTool Hotekys to assign/unassign aggregation 2023-05-28 14:45:57 +01:00
Bruno Postle 929aa3a49b Select new/changed objects in visual diff (#3096)
When visualising differences between current/selected revision,
new/changed objects are selected as well as colourised
2023-05-28 11:22:01 +01:00
Bruno Postle 83e97b7205 Fix checkout version equivalent to a remote HEAD 2023-05-28 11:02:46 +01:00
Dion Moult 5d583741b9 See #3184. Preserve parametric layerset axis direction hints when changing types. 2023-05-28 17:29:00 +10:00
Dion Moult d3d2c3608b Fix #2106. You can now assign an opening to another element. 2023-05-28 15:02:55 +10:00
Dion Moult a0edd66b92 Fix #3050. Unlinking now gives you an option to delete as well. 2023-05-28 13:52:23 +10:00
Dion Moult c8c0c9a621 Fix #3169. Don't allow the user to create invalid material sets without items. 2023-05-27 22:50:59 +10:00
Dion Moult 00c010e04d See #3169. Fix bug where non walls and slabs didn't get semantic classes in drawings. 2023-05-27 21:32:13 +10:00
Andrej730 1d2c9780da Revision Clouds - new annotation type
Short demonstration - https://github.com/IfcOpenShell/IfcOpenShell/assets/9417531/90c9b458-0bae-40a2-9eee-d748e57c24a3

Curently it doesn't have any special representation in viewport and it doesn't have any customisable parameters.

You might get come across error "Revision cloud edges must form just 1 polygon" - it means your edges either don't form any polygon or form 2 or more, both cases are not supported.
2023-05-27 15:22:07 +05:00
Dion Moult adec3853e8 Fix #3202. 2023-05-26 19:54:26 +10:00
Dion Moult d3251c5ca1 New get_shape_matrix utility. 2023-05-26 19:31:01 +10:00
Dion Moult 68f8d8798a Fix #3181. 2023-05-26 19:31:01 +10:00
Bruno Postle 4c793be5f7 BBIM Ensure LF end-of-line in Git repo (#3096)
Git auto text detection seems to fail with IFC files, possibly due to
long lines. So we now force detection as text which ensures conversion
from CRLF to LF when adding to the repository. We also convert the local
IFC file to LF otherwise Git thinks that the local file is out-of-sync
with the repo version ¯\_(ツ)_/¯
2023-05-26 08:28:20 +01:00
Andrej730 812eb5eaef Small documentation note 2023-05-25 16:09:43 +05:00
Andrej730 6c2be8f886 Procedurally generating list of props for shading styles to cover more props
If it will be too much we still have option to exclude some of them
2023-05-25 14:50:45 +05:00
Gorgious56 301d4fecf8 Remove the cryptic (and slightly annoying) console warning logs when trying to display the type manager when there is no element type in the file 2023-05-24 16:54:57 +02:00
Gorgious56 604dfa8cf4 Fix annoying tooltip when using the eyedropper to copy a type 2023-05-24 16:06:33 +02:00
Andrej730 8bb4ab91b8 Fixed problem with ifccsv set_element_key #3192
Currently "type" attribute in selector returns something like "#71=IfcWallType('20jY5HfLH4LBwMrDEoTdML',$,'WAL100',$,$,$,$,$,$,.NOTDEFINED.)" when the code was expecting the class name like "IfcSlab" or "IfcWall". Changed it to "class" atribute that returns just class name.
2023-05-24 18:02:53 +05:00
Andrej730 0cdd9a0cde Window Modifier - support typing units in props
Example - https://i.imgur.com/C99RRs9.png
2023-05-24 17:41:10 +05:00
Andrej730 4768ea23e4 More descriptive exception for reassign_class #3192
Added it because there was no error handling here anyway and later there was another UnboundLocalError:

    for attribute in declaration.all_attributes():
UnboundLocalError: local variable 'declaration' referenced before assignment
2023-05-24 16:45:07 +05:00
Andrej730 659cbe69c3 Stair Modifier - support typing units in props
Example - https://i.imgur.com/XQsVge1.png
2023-05-24 16:45:07 +05:00
Andrej730 e22f4dd001 Array Modifier - support typing units in props
Example - https://i.imgur.com/a0r0vUs.png
Also accidentally fixed input_cursor_array not working properly for projects with non-meters units.
2023-05-24 16:45:07 +05:00
Andrej730 394b2003b3 Small error message on aggregating objects 2023-05-24 16:45:07 +05:00
Dion Moult d72ac7827b Fix #3193. Revert "partially addresses #3103."
This reverts commit 1b2ab4e3f0.
2023-05-24 21:31:41 +10:00
Dion Moult 610ec7d8ad Fix #3086. 2023-05-24 19:33:14 +10:00
Gorgious 8f41ae0c1b Using the eyedropper on a type automatically sets the type and validates assign_type. However it does display an annoying message saying the value was not set, I don't know how to get rid of it. 2023-05-24 11:10:35 +10:00
Gorgious f8113d4857 You can now eyedrop an object in the 3D viewport to fill in its class in the type selector 2023-05-24 11:10:35 +10:00
Gorgious 72169d06ad Fix Relating type enum not correctly initializing 2023-05-24 11:10:35 +10:00
Gorgious 2b78871c01 The type manager now switches to the type when clicking on the "select type" button. Duplicating a type also changes the active type in the type manager 2023-05-24 11:10:35 +10:00
Gorgious 1ce254cc9a Selecting types and instances now show a helpful tooltip when the object is not selectable (due to being hidden or in a hidden collection) 2023-05-24 11:10:35 +10:00
Gorgious 3b07c086a8 You can now duplicate a type from the properties panel
+ change the "paste" icon for the  "duplicate" icon
2023-05-24 11:10:35 +10:00
Gorgious ff7b101b71 "Select instances" button is now grayed out when the type doesn't have any instance 2023-05-24 11:10:35 +10:00
Bruno Postle 92405d819b BlenderBIM set gitattributes and autocrlf
Force Git to always treat IFC files as text.
Store IFC in Git with LF line-endings, but checked-out files are
CRLF on Windows.
2023-05-23 22:45:41 +01:00
Andrej730 c367989ebd Roof modifier - more descriptive errors on invalid footprints #3171 2023-05-23 18:46:27 +05:00
Andrej730 7cae099fb4 Default material layer thickness for adding new layers
Same as when you create IfcMaterialLayerSet
2023-05-23 16:20:06 +05:00
Andrej730 7dcccf9ea5 UI problem with creating empty material layer set causing issues #3169
Now it's also possible to select some material that will be used for they first layer of material layer set.
2023-05-23 15:59:15 +05:00
Ryan Schultz 535e974cc3 bulk assignment of objects to an aggregate. 2023-05-22 22:06:08 -05:00
Andrej730 629aff1e50 Added a descriptive error on attempt of deleting last material layer 2023-05-22 16:37:31 +05:00
Andrej730 628b6830db Fixed error loading .ifc and activating view for drawing with underlay 2023-05-22 15:47:28 +05:00
Ryan Schultz 1b2ab4e3f0 partially addresses #3103. 2023-05-21 15:07:08 -05:00
Sigma Dimensions f97752c368 fix contracting/expanding schedule of rates 2023-05-21 13:25:15 +01:00
Sigma Dimensions d2d5db9bf6 Fix selecting unassigned work/cost schedule products 2023-05-21 12:59:54 +01:00
Sigma Dimensions a8554d9e56 small fix for work schedule derived start/end dates 2023-05-21 12:52:05 +01:00
Sigma Dimensions 11a38af901 BBIMD: enable deleting sequence relationships from list of predecessors/successors 2023-05-21 12:48:34 +01:00
Sigma Dimensions 27a4976b9d remove annoying print statement 2023-05-21 12:45:28 +01:00
Sigma Dimensions eb54f5a0cb ios api task fix: remove orphan IfcRelNests when deleting tasks 2023-05-21 12:45:04 +01:00
Sigma Dimensions a4a6dce6de BBIM D: Copy cost items, export Cost Schedules individually, start improving Cost Schedule UI 2023-05-21 12:42:50 +01:00
Sigma Dimensions 4184e25c68 Easier grouping of materials by allowing material attributes editing from the IfcMaterial subpanel in the scene properties tab 2023-05-21 12:33:41 +01:00
Bruno Postle d98b0766c2 Allow missing/broken GitPython
`import git` can fail badly with a broken xcode installation, so
we let blenderbim run anyway
2023-05-21 09:11:26 +01:00
Bruno Postle b8990dd0a4 Fix merging of remote branches (#3096)
You can now 'clone' a remote repository and 'push' to it. There is no 'pull'
functionality because this can fail badly, so there is a 'fetch' which
retrieves all remote branches without merging (and never fails). The
user can then select the remote branch in the branch pull-down and merge
it.
2023-05-20 09:25:33 +01:00
Andrej730 2198201e0b Detect BBIM railings at import to import as meshes not curves #3144
Sometimes railing could consist just of one IFCSWEPTDISKSOLID then it will be considered native swept disk solid and imported as a curve.

But when user create BBIM railing they edit it as a mesh and curve might look a bit different. Therefore we detect it now at import to make sure it's going to be reprsented the same way as it was saved.
2023-05-19 17:33:45 +05:00
Andrej730 53e58848f3 Reset active drawing id if we removed the active drawing
Previously there was an error if you remove all drawings:
```
Traceback (most recent call last):
  File "\addons\blenderbim\bim\module\drawing\ui.py", line 182, in draw
    DrawingsData.load()
  File "\addons\blenderbim\bim\module\drawing\data.py", line 106, in load
    "active_drawing_pset_data": cls.active_drawing_pset_data(),
  File "\addons\blenderbim\bim\module\drawing\data.py", line 134, in active_drawing_pset_data
    drawing = ifc_file.by_id(bpy.context.scene.DocProperties.active_drawing_id)
  File "\addons\blenderbim\libs\site\packages\ifcopenshell\file.py", line 326, in by_id
    return self[id]
  File "\addons\blenderbim\libs\site\packages\ifcopenshell\file.py", line 314, in __getitem__
    return entity_instance(self.wrapped_data.by_id(key), self)
  File "\addons\blenderbim\libs\site\packages\ifcopenshell\ifcopenshell_wrapper.py", line 4517, in by_id
    return _ifcopenshell_wrapper.file_by_id(self, id)
RuntimeError: Instance #1 077 not found
```
2023-05-19 15:40:58 +05:00
Andrej730 9c367d1702 fixed neg values in format_distance & section/plan level #3175 2023-05-19 15:28:45 +05:00
Andrej730 db18a81035 Added default Model/Annotation/PLAN_VIEW to support e68fdb2db 2023-05-19 12:33:56 +05:00
Andrej730 d1d6b00f92 Test that drawing styles are loaded 2023-05-19 12:00:00 +05:00
Andrej730 32fc147a7c Fixed error on rebuilding sheet with underlays
Also added description and more clear name for `bim.open_sheet`

Error was:
```
Error: Python: Traceback (most recent call last):
  File "\addons\blenderbim\bim\module\drawing\operator.py", line 82, in execute
    IfcStore.execute_ifc_operator(self, context)
  File "\addons\blenderbim\bim\ifc.py", line 410, in execute_ifc_operator
    result = getattr(operator, "_execute")(context)
  File "\addons\blenderbim\bim\module\drawing\operator.py", line 1177, in _execute
    del raster_references[reference.Location]
TypeError: list indices must be integers or slices, not str
```
2023-05-19 11:48:35 +05:00
Andrej730 d98054234c Update drawing styles on changing Underlay checkbox #3172 2023-05-19 10:52:52 +05:00
Ryan Schultz d6460026f2 allow elevations to show negative values
removed RL - not standard throughout the world
2023-05-18 22:38:12 -05:00
Bruno Postle 88ba6514e8 Git remote fetch and push operations (#3096) 2023-05-19 00:27:37 +01:00
Andrej730 894a688bd8 Added test scenario to reproduce #3169
Can be easily called with `pytest .\test\bim\test_feature.py -k "issue"`

Commented some lines out to make sure it doesn't interrupt someone's test process.

Need to uncomment `When I press "bim.create_drawing"` if you want to see if it freezes for you.

You can also uncomment `And I save sample test files and open in blender` to see the freeze manually in Blender client.
2023-05-18 18:36:07 +05:00
Andrej730 c69ead4893 Test to cover drawing maintaining sheet position #3155
Updated to gitignore to include some new folders that are created during tests
2023-05-18 17:30:04 +05:00
Andrej730 e68fdb2db7 3d annotations for FALL, SECTION_LEVEL, PLAN_LEVEL #3145
Before that commit if you created those types of annotations and then reopened .ifc file (without .blend) then you would lose their z coordinate and therefore would lose their value (because they were created previously as 2d annotations).

Now those annotations created as 3d to avoid that problem. I've also added temporary fallback that will turn your existing annotations to 3d next time you edit them.
2023-05-18 14:19:36 +05:00
Andrej730 ae9372c3d3 Maintaining drawing's sheet position on raster x/y changes #3155
Example - https://i.imgur.com/uiNWhIq.png
2023-05-17 19:23:42 +05:00
Andrej730 0365c6d43b More consistent PlanLevel and SectionLevel in viewport and svg
Now it's using first vert instead of last to calculate height (same way svgwriter does it)
2023-05-17 14:50:52 +05:00
Andrej730 18b6969221 Showing decorations on activating view #3146 2023-05-17 12:35:53 +05:00
Andrej730 a1935acb3d AngleDecorator - changed edges / arc colors
Now angle annotation edges (not the arc itself) are greyed out when you're not editing them - that way they won't draw unnecessary attention. When you're in edit mode they'll appear white and the angle arc will appear blue.

Demo - https://imgur.com/a/lsgp9G4
2023-05-17 12:20:08 +05:00
Andrej730 9548c5e657 Positioning AngleDecorator text at the center of the arc
Before - https://i.imgur.com/0VxFkg9.png
After - https://i.imgur.com/ibDnlbE.png
2023-05-17 11:52:14 +05:00
Andrej730 762299124f More smoothed lines for annotations with angles and circles 2023-05-17 11:32:01 +05:00
Andrej730 f682bf5688 Added some tests for changing ifc text 2023-05-17 11:18:24 +05:00
Andrej730 f7ed3a8727 Fixed bug with annotations update #3159
toggleDecorations was interrupted because it was trying to update text for non text element
2023-05-17 10:44:26 +05:00
Andrej730 c124506e41 Fixing bug with changing text size #3159
Bug appeared after 046940f but it was there before - it was just kind of compensated by the other bug.
Because of that bug blender text size value was reupdated before saving it to pset therefore pset never received a new value.

Also added some refactor, planned to write some test to get this error covered.
2023-05-16 17:58:26 +05:00
Andrej730 273cdc6ea8 Fixed ifc text annotation preview not showing during edit #3159
Error was:

```
Traceback (most recent call last):
  File "\scripts\addons\blenderbim\bim\module\drawing\decoration.py", line 1899, in __call__
    decorator.decorate(context, obj)
  File "\scripts\addons\blenderbim\bim\module\drawing\decoration.py", line 1451, in decorate
    self.draw_text(context, obj)
  File "\scripts\addons\blenderbim\bim\module\drawing\decoration.py", line 540, in draw_text
    symbol = text_data["Symbol"]
KeyError: 'Symbol'
```
2023-05-16 16:53:38 +05:00
Andrej730 0ef6ea6bd4 Schedule Builder - some naive support for font size in text #3154 2023-05-16 16:43:39 +05:00
Andrej730 607d9a06ec Schedule Builder - support for bold and italic text in cells #3154 2023-05-16 16:31:26 +05:00
Andrej730 1b472ca5f2 Schedule builder - fixing cell alignment #3154
Noticed that it's actually not merging cell style with col style as I did before but just prioritizing cell style if it's defined
2023-05-16 15:24:39 +05:00
Andrej730 18568d6ba7 Schedule cells limit without print range to prevent system freeze
What I've found that sometimes you might get into situation (especially if .ods was created / processed in Excel) you might get a lot unnecessary columns and rows (in fact excel always seems to save 1048576x16384 table) and trying to print this kind of table results in investing a lot of RAM into it and possible system freeze.

Now there is a check if table has more than 10000 cells (number is completely made up 😁) then it would require user to define print range in the table to prevent the issue.
2023-05-16 15:16:48 +05:00
Andrej730 bfb59dd387 Scheduler builder enhancements #3154
Now it supports
1) print ranges - in svg you'll get only the part that's included in print-range.
2) children styles - sometimes cells inherit center alignment from column styles and before that commit text wouldn't be centered in svg in that case
3) repeating rows support
2023-05-16 14:44:45 +05:00
Andrej730 38cf88d5d2 Saving drawing shading styles to ifc #2991
Now shading styles are stored in ifc. It's actually stored in json file (by default it's in projects "assets/shading_styles.json") and the path to it saved in `EPset_Drawing.ShadingStyles`.
Current shading styles is stored in `EPset_Drawing.CurrentShadingStyle`.

This change could potentially cause problems but I've added some temporary fallback to make sure `EPset_Drawing.ShadingStyles` will be set to some default path and default shading styles will be copied to the project's folder.
2023-05-15 16:43:56 +05:00
Bruno Postle 7ca6f9d7a1 BlenderBIM Git clone repository operator (#3096) 2023-05-14 21:10:53 +01:00
Gorgious 5874508263 use tool module instead of local function to activate object 2023-05-13 19:57:52 +02:00
Bruno Postle 954de793dd BlenderBIM Git tagging documentation 2023-05-13 10:37:29 +01:00
Bruno Postle df74d5efbe BlenderBIM list and create git tags #3096 2023-05-13 09:57:10 +01:00
Andrej730 f7b9a7bfb5 Multiline in dimension description / suffix / prefix #3005
Also added some offset for dimension text in viewport to make it more similar to svg
2023-05-12 14:54:22 +05:00
Thomas Krijnen 63eaa88d3d #3139 Capture parsed selected simple type values 2023-05-12 10:19:15 +02:00
Andrej730 f518521ea1 Option to add prefix and suffix to dimension annotation #3005
Example - https://imgur.com/a/0NkStWm
2023-05-11 17:38:48 +05:00
Andrej730 5e1ee22bcd Ifc CSV Export template now also saves the expression #3034
Previously saved templates wouldn't work now, they need to be either recreated or modified to make them work.

Example of old template format:
`["Name", "class", "type.Name"]`

Example of new template format:
`{"expression": ".IfcDoor", "attributes": ["Name", "class", "type.Name"]}`

Note that this would work too:
`{"attributes": ["Name", "class", "type.Name"]}`
2023-05-11 14:18:51 +05:00
Andrej730 ca09ff2ef0 Not all curve annotation types were detected at the import #3090 2023-05-11 10:54:27 +05:00
Bruno Postle 8643aff800 Merge branch 'v0.7.0' of github.com:IfcOpenShell/IfcOpenShell into v0.7.0 2023-05-10 22:53:00 +01:00
Gorgious 2e88993654 Fix #3128 : RefLatitude and RefLongitude attributes now show their description on right click + add a snippet to tell the user how to format the values 2023-05-10 22:51:30 +01:00
Bruno Postle b77849d279 Report ifcmerge conflicts in pop-up #3096 2023-05-10 22:45:22 +01:00
Gorgious c7e288466f Fix #3128 : RefLatitude and RefLongitude attributes now show their description on right click + add a snippet to tell the user how to format the values 2023-05-10 21:31:19 +02:00
Kristoffer Andersen bb48d4aac1 Update meta.yaml
pin daily conda builds to occt 7.7.0
2023-05-10 20:43:22 +02:00
Gorgious56 2b99cc1e58 Fix typo in get_pset docstring 2023-05-10 16:41:19 +02:00
Andrej730 ca286f8d19 Migrating shaders to builtins to support M1 #2897
Migrated all shaders from geometry shaders to builtins (all geometry data now calculated in python before passing to shader) to make them more reliable and support Metal backend on Mac M1 (tested that it works - both annotations and gizmo).

The current downside is that there is no more custom frag shaders too - meaning we do not support dashed lines in the annotations (currently Hidden and Grid just use a bit less bright annotation color).
2023-05-10 18:19:00 +05:00
Bruno Perdigão 73a3cc2cc5 changed custom scale input 2023-05-10 09:11:09 +10:00
Bruno Postle 169709ed04 Selecting a git revision updates info box 2023-05-09 22:15:57 +01:00
Sigma Dimensions 2e746242ce Fix task json issue due to "Bad control character in string literal in JSON at position XXXX" 2023-05-09 15:43:12 +01:00
Sigma Dimensions 951b8e2b15 Convenient Pie Menu shortcut to aggregate selected objects to active object 2023-05-09 14:24:31 +01:00
Sigma Dimensions ede3edcf1e fix toggle_cost_item_parent 2023-05-09 13:25:21 +01:00
Sigma Dimensions 184c9f87b2 Fix baseline gantt chart bug when new tasks are added to the planned schedule 2023-05-09 13:03:57 +01:00
Dion Moult 0793a4602d Minor improvements to shape utility. 2023-05-09 20:51:08 +10:00
Dion Moult bb285c47dc See #2673. Bundle IfcTester for PyPI. 2023-05-09 20:49:08 +10:00
arun 4cb6fc05d6 corrected get_extrusions function 2023-05-08 20:18:57 +02:00
Kristoffer Andersen a302e5db64 add zlib to conda build dependency
zlib is now longer indirectly included from the vtk package now that we have a new occt variant novtk that does not depend on vtk
2023-05-08 14:12:03 +02:00
Bruno Postle ff21749da4 Merge branch 'v0.7.0' of github.com:IfcOpenShell/IfcOpenShell into v0.7.0 2023-05-07 13:12:22 +01:00
Bruno Postle 23b5da7670 BlenderBIM ifc file association (linux only) #2399 2023-05-07 13:08:28 +01:00
Bruno Postle c31e479f47 Rename blenderbim.sh, better initialisation #2399 2023-05-07 09:46:15 +01:00
Dion Moult 260eb31a69 Add shape utility to easily get profiles and extrusions 2023-05-07 17:32:46 +10:00
Dion Moult 1fb6dac6fb Fix failing tests in preparation for release. 2023-05-06 11:05:13 +10:00
Dion Moult 34d947c51f Fix #3084. Issue where drawings with no elements couldn't be generated. 2023-05-06 11:02:41 +10:00
Sigma Dimensions 23f7a6d5a6 fix test-related sequence bug
Fix baseline gantt chart bug
2023-05-06 00:21:57 +01:00
Sigma Dimensions b15926c20a Fix baseline gantt chart bug when new tasks are added to the planned schedule 2023-05-05 23:43:27 +01:00
Sigma Dimensions 0a7ef80ca7 BBIM4D: Enable filtering by active work schedule when retrieve tasks related to objects 2023-05-05 18:26:32 +01:00
Sigma Dimensions be08406cc1 Auto-refresh task sorting 2023-05-05 17:11:24 +01:00
Bruno Postle cc9777b1e8 Basic user documentation for BlenderBIM Git functionality (#3096) 2023-05-04 23:53:37 +01:00
Dion Moult 539b3a243b Fix fundamental bug where area and volume units were ignored on new projects. 2023-05-04 21:43:48 +10:00
Andrej730 41e58a3d46 Small addition to 8c302ed85 2023-05-04 16:00:12 +05:00
Andrej730 8c302ed85c Changed x_angle subtype to degrees
Now BIMModelProperties.x_angle stores data in radians internally but for user it still appears as degrees
2023-05-04 15:53:29 +05:00
Dion Moult dc4dc46b62 See #1676. Fix space boundary generation for IFC4 RV where openings are already subtracted from the body. 2023-05-04 20:22:29 +10:00
Dion Moult a07fa5a2c2 Accommodate invalid named units when loading IFCs. 2023-05-04 19:55:54 +10:00
Dion Moult ad9dbce003 See #3085. Don't bundle numpy as Blender already has it and it causes conflicts. 2023-05-04 19:45:04 +10:00
Andrej730 d0526d0ae5 Updating BIM Tools props on active object change #3102
Updating extrusion_depth, length and x_angle on active object change. Shouldn't be too expensive and that way it'll be more consistent with Blender UI.
2023-05-04 12:55:40 +05:00
Andrej730 75e9c3265d Fallback to abs path adding schedule rel path is not available #3106 2023-05-04 11:32:05 +05:00
Andrej730 b37705ab6e Updating drawing's size on sheet to match it's scale #2965 2023-05-04 11:25:16 +05:00
Massimo Fabbro d9d5f86a6e Now it's possible to regenerate a space that it was created automatically from walls 2023-05-04 11:37:42 +10:00
Sigma Dimensions 1f3ad95c00 remove annoying print statement, run black on cost 2023-05-03 19:46:15 +01:00
Sigma Dimensions 9e360f7efa remove annoying print statement, run black on cost 2023-05-03 19:45:01 +01:00
Sigma Dimensions 64fd6a1f52 fix copying relationships when duplicating tasks 2023-05-03 19:43:05 +01:00
Sigma Dimensions ba04df4ce3 BBIM 4D: implement human readable duration to read/edit durations.
new IOS Fuzzy Duration Parsing utility
2023-05-03 16:09:27 +01:00
Sigma Dimensions cd27a026fd fix editing null task duration values #3091 2023-05-03 16:01:58 +01:00
Dion Moult baedf71106 Minor documentation update to describe how add-ons with conflicting deps / hotkeys are incompatible. 2023-05-04 00:31:44 +10:00
Dion Moult 4f6c95b119 See #1676. Space boundary generation now accounts for voids and fills. 2023-05-04 00:31:24 +10:00
Dion Moult b1761ac393 See #1676. Space boundaries default to physical internal boundaries. 2023-05-03 21:21:55 +10:00
Dion Moult fcdf3e583f See #1676. You can now set the type of boundary class prior to generating boundaries. 2023-05-03 21:03:54 +10:00
Andrej730 2001119dff A bit more intuitive adding drawing/schedule to the sheet
Now it will detect currently active sheet even if you have sheet's drawing/titleblock/schedule selected.
2023-05-03 15:39:53 +05:00
Dion Moult 60a9d96ac8 See #1676. You can now duplicate space boundaries. 2023-05-03 20:38:09 +10:00
Andrej730 6bd177d250 Annotation Tool - always show "Add type" button
It wasn't showing if you had some objects selected which seems a bit counterintuitive - the need to deselect everything to create a new type.
2023-05-03 15:00:58 +05:00
Andrej730 1ece84e31d Fixed error during building schedule from ods
We were discarding some styles that didn't have any style properties and then we always needed to keep it in mind later on, now we just create empty dict for those.
2023-05-03 14:55:45 +05:00
Andrej730 40ea9457a8 Apply scale and rotation to ifc text symbols #3094
Before symbols attached to ifc text were not rotated / scaled if they didn't have "text-template" fields in them.
2023-05-03 14:39:43 +05:00
Andrej730 e95d91a91e Support multiline text in the schedule #3010
Before - https://i.imgur.com/gff5jo3.png
After - https://i.imgur.com/meyXBYb.png
2023-05-03 12:33:07 +05:00
Andrej730 c8fb92175f Added some (naive) text wrapping in the schedule #3010
Before - https://i.imgur.com/dJPQ788.png
After - https://i.imgur.com/T1FtI8H.png

For it to work make sure you have word wrapping in ods.

It's still very naive since it's taking constant font width which could vary depending on font type, font size, font lettering. The proper way would be is to either find some svg way to do it (couldn't find any stable one) or to render text to identify it's width (could be done with PIL but seems overkill, atleast for now).
2023-05-03 12:25:08 +05:00
Sigma Dimensions a6fb4cbfbf BBIM 4D: Enable displaying task tooltip in the gantt chart & add resource columns 2023-05-03 04:34:55 +01:00
Sigma Dimensions 30bb6d05c5 BBIM 4D: You can now visualize & print work schedule baselines with Gantt Charts 2023-05-03 04:33:16 +01:00
Sigma Dimensions 9422f804fb minor fix 2023-05-03 04:22:02 +01:00
Sigma Dimensions 415a7746be BBIM 4D Gantt: Support multiple languages, human readable durations, add convenient Print Function with different Sheet Formats,
start improving overall page style; refactor code to split js and css code away from mustache file
2023-05-02 17:27:47 +01:00
Andrej730 ee903208dc Small fix for text alignment in scheduler 2023-05-02 17:41:56 +05:00
Dion Moult f89e834fea Fix #3085. Prioritise shipped BlenderBIM Add-on packages instead of system packages to avoid dependency clashes. 2023-05-02 22:20:34 +10:00
Dion Moult e8b4335741 Minor UI cleanup. Remove align buttons from architectural tool panel. 2023-05-02 22:20:34 +10:00
Dion Moult 83010f0542 See #1676. You can now add basic space boundaries with the BIM Tool with Shift-B, either by selecting both a space and an element (e.g. wall) or selecting a space and placing the 3D cursor on a face. 2023-05-02 22:20:34 +10:00
Dion Moult 04e5f5036c See #1676. Deleting space boundaries now also removes connection geometry. 2023-05-02 22:20:34 +10:00
Andrej730 5d86d38a14 Support cells text alignment building schedule from ods #2885
Before - https://i.imgur.com/9QZ2YW2.png
After - https://i.imgur.com/M7ZHSJq.png
2023-05-02 17:04:48 +05:00
Andrej730 a82db1f622 Support merged columns building schedule from ods #2885
Before - https://i.imgur.com/p1l6ugn.png
After - https://i.imgur.com/9QZ2YW2.png
2023-05-02 16:24:30 +05:00
Andrej730 fb618d536b Keep rows height building svg schedule from ods #2885
Before - https://i.imgur.com/erMH4Ku.png
After - https://i.imgur.com/p1l6ugn.png
2023-05-02 14:56:05 +05:00
Andrej730 23f96779fa More safe adding roof modifier #3080 2023-05-02 10:38:25 +05:00
Dion Moult 7b67854654 Fix #3088. Prioritise body curves to be loaded before trying to load 2D annotations. 2023-05-02 13:11:01 +10:00
Sigma Dimensions 2e7f941465 BBIM 4D: Auto Update Resource Usage from resource tree 2023-05-02 02:47:22 +01:00
Sigma Dimensions 1c1706fde3 BBIM feature to create & view work schedule baselines 2023-05-02 02:43:54 +01:00
Sigma Dimensions ca77a895e0 IOS.API consider removing baseline tasks when deleting tasks 2023-05-02 02:31:48 +01:00
Sigma Dimensions 0f447f964e IOS.API usecase to create a work schedule baseline 2023-05-02 02:30:38 +01:00
Sigma Dimensions 2c31fc6b25 run black 2023-05-02 02:27:32 +01:00
Dion Moult 0ba79dc105 Accommodate space boundaries with no connection geometry, fetch spatial structure for IFC2X3, and fix scale bug when editing boundary profiles. 2023-05-02 10:23:54 +10:00
dependabot[bot] 52c686b656 Bump flask from 2.0.1 to 2.3.2 in /src/opencdeserver
Bumps [flask](https://github.com/pallets/flask) from 2.0.1 to 2.3.2.
- [Release notes](https://github.com/pallets/flask/releases)
- [Changelog](https://github.com/pallets/flask/blob/main/CHANGES.rst)
- [Commits](https://github.com/pallets/flask/compare/2.0.1...2.3.2)

---
updated-dependencies:
- dependency-name: flask
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-02 10:06:31 +10:00
Thomas Krijnen 64d98f1295 #2676 2023-05-01 20:16:39 +02:00
Andrej730 ceb2c4075c Option to scale symbol from ifc tag by scaling ifc text object #2946
Also added some text scaling in viewport if there is some symbol associated with text annotation to roughly visually indicate how big this thing is going to be in the svg.

Example - https://user-images.githubusercontent.com/9417531/235445393-447f3aba-8425-4da6-81d0-6bf9004fcf2b.mp4
2023-05-01 16:25:04 +05:00
Andrej730 166e7d52ec Keep text rotation for text annotation with symbols #2946 2023-05-01 16:25:04 +05:00
Dion Moult 644c2652ea See #1676. You can now toggle boundaries on or off in the BIM tool via Alt-B, with a special boundary decorator. 2023-05-01 21:19:29 +10:00
Andrej730 63b201fd95 Test command to load blend file during test scenario for debugging 2023-05-01 15:35:27 +05:00
Andrej730 d70616056c Moving decorators colors from scene to addon props #3033 2023-05-01 14:54:47 +05:00
Dion Moult a65e376886 See #1676. Tab now toggles space boundary surface profile editing. 2023-05-01 19:40:49 +10:00
Andrej730 a985c70f0c Fixed error on adding Roof modifier to non-footprint objects #3080 2023-05-01 14:03:28 +05:00
Andrej730 0d09e02e7e Fixed bug with double conversion of height/extrusion length #3073
Also fixed some tests
2023-05-01 13:46:24 +05:00
Thomas Krijnen 23e3551f27 Fix regex escape in rule_executor.py 2023-04-30 19:09:43 +02:00
Shohei Kunimatsu 885bca0865 Fix wrong attribute name usage (ValueComponent -> UnitComponent) in IfcFile::getUnit 2023-04-30 19:06:58 +02:00
Sigma Dimensions 0f874dad51 BBIM 4D features & fixes:
- feature to re-order tasks
- fixes for task sorting
- further workSchedule UI/UX improvements
- forgotten bits and bobs
2023-04-30 15:44:13 +01:00
Sigma Dimensions 107873cf6e - refactor IOS.API resource calculation to re-use shared utilities
- run black
2023-04-30 15:35:17 +01:00
Sigma Dimensions 4a8f5af7ba - BBIM 4D: features to review and edit Resource productivity data on the fly
- IOS: new resource utilitiy functions
2023-04-30 15:31:36 +01:00
Dion Moult 83084aeb7b Add profile-style (like slab editing) enable editing, edit, and disable editing operators for energy boundary surface geometry. 2023-04-30 20:57:51 +10:00
Dion Moult 51640ccc28 Fix #3030. Don't bother layerset slicing walls with a single layer. 2023-04-30 13:43:43 +10:00
Dion Moult aefbc134ea Cut merging now only applies to particular elements for the majority of cases. Drawing generation can be twice as fast due to this. 2023-04-30 13:43:43 +10:00
Massimo Fabbro 29e1d70334 Generate space from walls refactor and cleaning around 2023-04-29 22:33:19 +02:00
Massimo Fabbro 9118e529e6 Add generate space button in UI in N-Panel -> BlenderBim -> Architectural 2023-04-29 22:33:19 +02:00
Massimo Fabbro 8ee0a1611d Now it's possible to create spaces automatically from selected walls 2023-04-29 22:33:19 +02:00
Sigma Dimensions 2af4388f40 Fix duplicate task logic in IOS.API 2023-04-29 13:53:04 +01:00
Dion Moult 93a2d18ea6 Filter elements visible in the camera view. Drawing generation is now significantly faster on larger projects (e.g. 24s down to 10s for a portion of the Revit sample project) 2023-04-29 16:33:44 +10:00
Dion Moult fc49859ea4 Fallback to unmerged polygons if merging fails. More investigation needed. 2023-04-29 16:30:40 +10:00
Dion Moult c0ac9cb380 Viewport layerset slicing now uses a cache for speed. 2023-04-28 22:46:49 +10:00
Andrej730 d5cc6c3c8e Fixed double conversion in bim tool props after b4b6404
No more 1000000 and 3000000 mm values
2023-04-28 17:32:19 +05:00
Andrej730 9b22b5000f Added support for different railing cap types
Very similar to this - https://i.imgur.com/SNyvfgh.png
2023-04-28 17:24:41 +05:00
Dion Moult 3b69a4cc78 Fix bug where numerical tolerance caused certain objects to be unnecessarily updated. 2023-04-28 22:15:09 +10:00
Dion Moult adf54efcc5 Fix bug in cut decorator layersets where walls could be joined to odd non-layered things. 2023-04-28 22:14:26 +10:00
Dion Moult f6962eb58e Fix #3053. Bug where very thin meshes (<1mm) couldn't be reverse engineered into extrusions. 2023-04-28 21:05:13 +10:00
Andrej730 11601d6532 Fixed bug with wrong position of support in wall mounted railing
The problem was that that support's disk was not rotated according to the railing path.
2023-04-28 11:43:15 +05:00
Sigma Dimensions be2131d62c BBIM 4D: Auto-refresh task re-ordering 2023-04-27 13:58:08 +01:00
Dion Moult 3ffbf46ac2 Fix #3066. Regression crash when deleting openings. 2023-04-27 22:24:12 +10:00
Sigma Dimensions 3943107eec forgotten core functions & run black 2023-04-27 13:05:51 +01:00
Sigma Dimensions 94e3f75341 Sequence Tools clean-up 2023-04-27 13:03:32 +01:00
Sigma Dimensions 8bc8b950e7 BBIM 4D: Features to select a Work Schedule's assigned / unassigned products 2023-04-27 12:55:31 +01:00
Sigma Dimensions 05a95ac666 Work Schedule Panel UX improvement 2023-04-27 12:53:27 +01:00
Sigma Dimensions f016c803e4 Fix schedule task sorting 2023-04-27 12:49:27 +01:00
Dion Moult bac0dd37fa Fix #3052. Deep deletion of large geometries is now thousands of times faster. 2023-04-27 20:15:17 +10:00
Bruno Postle 75a37cf737 Fix crash when switching between git revisions 2023-04-26 23:50:36 +01:00
Bruno Postle bb7e9c4610 Fix check for invalid Git branch name 2023-04-26 22:25:27 +01:00
555 changed files with 82856 additions and 31099 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",
+57
View File
@@ -0,0 +1,57 @@
name: ci-ifctester-pypi
on:
schedule:
# ┌───────────── minute (0 - 59)
# │ ┌───────────── hour (0 - 23)
# │ │ ┌───────────── day of the month (1 - 31)
# │ │ │ ┌───────────── month (1 - 12 or JAN-DEC)
# │ │ │ │ ┌───────────── day of the week (0 - 6 or SUN-SAT)
# * * * * *
- cron: "0 0 18 * *"
push:
paths:
- '.github/workflows/ci-ifctester-pypi.yml'
workflow_dispatch:
env:
major: 0
minor: 0
name: ifcopenshell
jobs:
activate:
runs-on: ubuntu-latest
if: |
github.repository == 'IfcOpenShell/IfcOpenShell'
steps:
- name: Set env
run: echo ok go
build:
needs: activate
name: ${{ matrix.config.name }}-${{ matrix.pyver }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2 # https://github.com/actions/setup-python
with:
python-version: '3.10' # Version range or exact version of a Python version to use, using SemVer's version range syntax
architecture: 'x64' # optional x64 or x86. Defaults to x64 if not specified
- run: echo ${{ env.DATE }}
- name: Get current date
id: date
run: echo "::set-output name=date::$(date +'%y%m%d')"
- name: Compile
run: |
pip install build
cd src/ifctester &&
make dist
- name: Publish a Python distribution to PyPI
uses: ortega2247/pypi-upload-action@master
with:
user: __token__
password: ${{ secrets.PYPI_API_TOKEN }}
packages_dir: src/ifctester/dist
+8
View File
@@ -86,7 +86,15 @@ src/blenderbim/blenderbim/libs
# blenderbim test temp files
src/blenderbim/test/files/temp
src/blenderbim/drawings
src/blenderbim/layouts
# ifcopenshell swig and compiled files
src/ifcopenshell-python/ifcopenshell/_ifcopenshell_wrapper.so
src/ifcopenshell-python/ifcopenshell/ifcopenshell_wrapper.py
# apple
.DS_Store
# Brickschema
src/blenderbim/blenderbim/bim/schema/Brick.ttl
+5 -1
View File
@@ -2,8 +2,12 @@
IfcOpenShell
============
<p align="center">
<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
parsing support is provided for [IFC2x3 TC1], [IFC4 Add2 TC1], IFC4x1, IFC4x3, and IFC4x3. Extensive geometric support
parsing support is provided for [IFC2x3 TC1], [IFC4 Add2 TC1], IFC4x1, IFC4x2, and IFC4x3. Extensive geometric support
is implemented for the IFC releases [IFC2x3 TC1] and [IFC4 Add2 TC1]. Extending with support for arbitrary IFC schemas
is possible at compile-time when using C++ and at run-time when using Python.
+40
View File
@@ -0,0 +1,40 @@
# Dockerfile for Running AWS Lambda Function with Python and IfcOpenShell
# Base image: Python 3.9 from AWS's public container registry
FROM public.ecr.aws/docker/library/python:3.9 AS build
# Set the location of your Lambda function code
ARG FUNCTION_DIR="/var/task"
# Install necessary packages
RUN apt-get -y update && apt-get -y install unzip curl
# Install AWS Lambda runtime interface client
RUN pip install --target ${FUNCTION_DIR} awslambdaric
# Set the IfcOpenShell build version (check available builds at: https://blenderbim.org/docs-python/ifcopenshell-python/installation.html)
ARG IFC_OPENSHELL_BUILD="39-v0.7.0-476ab50"
# Download and extract IfcOpenShell
RUN curl https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-${IFC_OPENSHELL_BUILD}-linux64.zip -O && \
unzip ifcopenshell-python-${IFC_OPENSHELL_BUILD}-linux64.zip && \
mv ifcopenshell ${FUNCTION_DIR} && \
rm ifcopenshell-python-${IFC_OPENSHELL_BUILD}-linux64.zip
# Copy the requirements file and install dependencies
COPY requirements.txt .
RUN pip install -r requirements.txt
# Copy the Lambda function code
COPY ./example_handler ${FUNCTION_DIR}/example_handler
# Set the working directory
WORKDIR ${FUNCTION_DIR}
# Set the entrypoint for the Lambda function
ENTRYPOINT [ "/usr/local/bin/python", "-m", "awslambdaric" ]
# Set the Python import path to the Lambda function handler
# This path is relative to the root of the Lambda function code (FUNCTION_DIR)
# Lambda invocation will look for this path
CMD ["example_handler.extract_wall_psets_handler"]
+27
View File
@@ -0,0 +1,27 @@
import ifcopenshell
import ifcopenshell.util.element
import boto3
s3 = boto3.client('s3')
def extract_wall_psets_handler(event, context):
print("Hello from lambda")
print("Event: {}".format(event))
print("Context: {}".format(context))
filename = event['body']['filename']
s3.download_file('my_ifc_files_bucket', filename, f'/tmp/{filename}')
ifc_file = ifcopenshell.open(f'/tmp/{filename}')
walls = ifc_file.by_type('IfcWall')
psets = []
for wall in walls:
wall_data = ifcopenshell.util.element.get_psets(wall)
wall_data['id'] = wall.GlobalId
psets.append(wall_data)
return {
'statusCode': 200,
'body': psets
}
+1
View File
@@ -0,0 +1 @@
boto3
+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)
+68 -11
View File
@@ -21,7 +21,7 @@ cmake_minimum_required(VERSION 3.1.3)
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED ON) # not necessary, but encouraged
project (IfcOpenShell VERSION 0.7.0)
project(IfcOpenShell VERSION 0.7.0)
cmake_policy(SET CMP0048 NEW)
cmake_policy(SET CMP0074 NEW)
@@ -54,12 +54,14 @@ OPTION(USE_VLD "Use Visual Leak Detector for debugging memory leaks, MSVC-only."
OPTION(USE_MMAP "Adds a command line options to parse IFC files from memory mapped files using Boost.Iostreams" OFF)
OPTION(BUILD_SHARED_LIBS "Build IfcParse and IfcGeom as shared libs (SO/DLL)." OFF)
OPTION(MSVC_PARALLEL_BUILD "Multi-threaded compilation in Microsoft Visual Studio (/MP)" OFF)
OPTION(NO_WARN "Disable all warnings" OFF)
OPTION(WASM_BUILD OFF)
if (${HAS_MAX})
OPTION(BUILD_IFCMAX "Build IfcMax, a 3ds Max plug-in, Windows-only." OFF)
endif()
if (${BUILD_CONVERT})
OPTION(GLTF_SUPPORT "Build IfcConvert with glTF support (requires json.hpp)." OFF)
OPTION(USD_SUPPORT "Build IfcConvert with USD support (requires pixar's USD library)." OFF)
endif()
OPTION(USERSPACE_PYTHON_PREFIX "Installs IfcPython for the current user only instead of system-wide." OFF)
OPTION(ADD_COMMIT_SHA "Add commit sha and branch in version number, warning results in many rebuilds, requires git" OFF)
@@ -72,6 +74,14 @@ if(MSVC AND MSVC_PARALLEL_BUILD)
add_definitions("/MP")
endif()
if(NO_WARN)
if(MSVC)
add_compile_options("/w")
else()
add_compile_options("-w")
endif()
endif()
# QtViewer requires Qt5
OPTION(BUILD_QTVIEWER "Build IfcOpenShell Qt GUI Viewer" OFF)
include(GNUInstallDirs)
@@ -137,11 +147,6 @@ UNIFY_ENVVARS_AND_CACHE(PYTHON_EXECUTABLE)
UNIFY_ENVVARS_AND_CACHE(HDF5_INCLUDE_DIR)
UNIFY_ENVVARS_AND_CACHE(HDF5_LIBRARY_DIR)
UNIFY_ENVVARS_AND_CACHE(HDF5_LIBRARIES)
endif()
UNIFY_ENVVARS_AND_CACHE(BOOST_ROOT)
UNIFY_ENVVARS_AND_CACHE(BOOST_LIBRARYDIR)
if (NOT MINIMAL_BUILD)
UNIFY_ENVVARS_AND_CACHE(CGAL_INCLUDE_DIR)
UNIFY_ENVVARS_AND_CACHE(CGAL_LIBRARY_DIR)
UNIFY_ENVVARS_AND_CACHE(GMP_INCLUDE_DIR)
@@ -149,6 +154,8 @@ UNIFY_ENVVARS_AND_CACHE(GMP_LIBRARY_DIR)
UNIFY_ENVVARS_AND_CACHE(MPFR_INCLUDE_DIR)
UNIFY_ENVVARS_AND_CACHE(MPFR_LIBRARY_DIR)
endif()
UNIFY_ENVVARS_AND_CACHE(BOOST_ROOT)
UNIFY_ENVVARS_AND_CACHE(BOOST_LIBRARYDIR)
if(WASM_BUILD)
# when using the nix/build-all.py build script we should not
@@ -170,6 +177,54 @@ add_definitions(-DWITH_GLTF)
set(SWIG_DEFINES ${SWIG_DEFINES} -DWITH_GLTF)
endif()
# Add USD support to serializers
if(NOT MINIMAL_BUILD AND USD_SUPPORT)
UNIFY_ENVVARS_AND_CACHE(USD_INCLUDE_DIR)
UNIFY_ENVVARS_AND_CACHE(USD_LIBRARY_DIR)
if("${USD_INCLUDE_DIR}" STREQUAL "")
find_path(USD_INCLUDE_DIR pxr.h
[PATHS
/usr/include/pxr
/usr/local/include/pxr
]
REQUIRED
)
if(USD_INCLUDE_DIR)
message(STATUS "Found USD include files in: ${USD_INCLUDE_DIR}")
else()
message(FATAL_ERROR "Unable to find USD include directory, specify USD_INCLUDE_DIR manually.")
endif()
else()
set(USD_INCLUDE_DIR ${USD_INCLUDE_DIR} CACHE FILEPATH "USD header files")
message(STATUS "Looking for USD include files in: ${USD_INCLUDE_DIR}")
endif()
set(USD_LIBRARIES
usd_usd
usd_usdGeom
usd_usdShade
usd_usdLux
usd_vt
usd_sdf
usd_tf
usd_gf
)
find_library(USD_LIBRARY
NAMES ${USD_LIBRARIES}
PATHS ${USD_LIBRARY_DIR})
if(USD_LIBRARY)
message(STATUS "USD libraries ${USD_LIBRARIES} found in: ${USD_LIBRARY_DIR}")
link_directories(${USD_LIBRARY_DIR})
else()
message(FATAL_ERROR "Unable to find USD libraries in: ${USD_LIBRARY_DIR}")
endif()
add_definitions(-DWITH_USD)
set(SWIG_DEFINES ${SWIG_DEFINES} -DWITH_USD)
endif()
# Set INSTALL_RPATH for target
MACRO(SET_INSTALL_RPATHS _target _paths)
SET(${_target}_rpaths "")
@@ -633,6 +688,7 @@ ENDIF()
INCLUDE_DIRECTORIES(${INCLUDE_DIRECTORIES} ${OCC_INCLUDE_DIR} ${OPENCOLLADA_INCLUDE_DIRS}
${Boost_INCLUDE_DIRS} ${LIBXML2_INCLUDE_DIR} ${JSON_INCLUDE_DIR} ${HDF5_INCLUDE_DIR}
${USD_INCLUDE_DIR}
)
function(files_for_ifc_version IFC_VERSION RESULT_NAME)
@@ -841,9 +897,9 @@ endforeach()
add_library(Serializers ${SERIALIZERS_FILES})
set_target_properties(Serializers PROPERTIES COMPILE_FLAGS "-DIFC_GEOM_EXPORTS ${CONVERT_PRECISION}" VERSION "0.6.0" SOVERSION "0.6")
TARGET_LINK_LIBRARIES(Serializers ${SERIALIZER_SCHEMA_LIBRARIES} ${OPENCOLLADA_LIBRARIES})
TARGET_LINK_LIBRARIES(Serializers ${SERIALIZER_SCHEMA_LIBRARIES} ${OPENCOLLADA_LIBRARIES} ${USD_LIBRARIES})
endif(BUILD_CONVERT or BUILD_IFCPYTHON)
endif(BUILD_CONVERT OR BUILD_IFCPYTHON)
if (BUILD_CONVERT)
@@ -854,7 +910,7 @@ set(IFCCONVERT_FILES ${IFCCONVERT_CPP_FILES} ${IFCCONVERT_H_FILES})
ADD_EXECUTABLE(IfcConvert ${IFCCONVERT_FILES})
set_target_properties(IfcConvert PROPERTIES COMPILE_FLAGS "${CONVERT_PRECISION}")
TARGET_LINK_LIBRARIES(IfcConvert ${IFCOPENSHELL_LIBRARIES} ${OPENCASCADE_LIBRARIES} ${Boost_LIBRARIES} ${HDF5_LIBRARIES})
TARGET_LINK_LIBRARIES(IfcConvert ${IFCOPENSHELL_LIBRARIES} ${OPENCASCADE_LIBRARIES} ${Boost_LIBRARIES} ${HDF5_LIBRARIES} ${USD_LIBRARIES})
if ((NOT WIN32) AND BUILD_SHARED_LIBS)
# Only set RPATHs when building shared libraries (i.e. IfcParse and
@@ -897,7 +953,7 @@ find_package (Git)
if (GIT_FOUND)
message("git found: ${GIT_EXECUTABLE} with version ${GIT_VERSION_STRING}")
execute_process(
COMMAND ${GIT_EXECUTABLE} branch --contains HEAD
COMMAND ${GIT_EXECUTABLE} branch -a --contains HEAD
OUTPUT_VARIABLE git_branches
OUTPUT_STRIP_TRAILING_WHITESPACE
)
@@ -906,7 +962,8 @@ if (GIT_FOUND)
string(REPLACE "*" "" git_branch_candidate_temp "${git_branch_candidate}")
string(STRIP "${git_branch_candidate_temp}" git_branch_candidate_2)
if (NOT git_branch_candidate_2 MATCHES "^HEAD$")
set(git_branch ${git_branch_candidate_2})
string(REPLACE "/" ";" git_branch_candidate_2_list "${git_branch_candidate_2}")
list(GET git_branch_candidate_2_list -1 git_branch)
endif()
endforeach()
execute_process(
+1 -1
View File
@@ -41,4 +41,4 @@ cmake -G Ninja \
ninja
ninja install -j 2
ninja install -j 1
+4 -2
View File
@@ -24,7 +24,7 @@ requirements:
host:
- python
- boost-cpp
- occt
- occt ==7.7.0
- libxml2
- cgal-cpp
- hdf5
@@ -32,11 +32,12 @@ requirements:
- gmp # [unix]
- mpir # [win]
- nlohmann_json
- zlib
run:
- python
- boost-cpp
- {{ pin_compatible('occt', max_pin='x.x.x') }}
- occt ==7.7.0
- libxml2
- cgal-cpp
- hdf5
@@ -44,6 +45,7 @@ requirements:
- gmp # [unix]
- mpir # [win]
- nlohmann_json
- zlib
test:
imports:
+20
View File
@@ -0,0 +1,20 @@
# This CITATION.cff file was generated with cffinit.
# Visit https://bit.ly/cffinit to generate yours today!
cff-version: 1.2.0
title: bcf
message: >-
If you use this software, please cite it using the
metadata from this file.
type: software
authors:
- name: "IfcOpenShell contributors"
repository-code: >-
https://github.com/IfcOpenShell/IfcOpenShell/tree/v0.7.0/src/bcf
abstract: >-
Library to read and write BCF-XML and query OpenCDE
BCF-API modules
keywords:
- BCF
- IFC
license: LGPL-3.0-or-later
+1 -1
View File
@@ -11,7 +11,7 @@ name = "bcf-client"
description = "BCF-XML file handler."
readme = "README.md"
requires-python = ">=3.8"
keywords = ["IFC", "BCF", "BIM", "eingineering"]
keywords = ["IFC", "BCF", "BIM"]
dependencies = [
"xsdata",
"numpy",
+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
+26
View File
@@ -0,0 +1,26 @@
# This CITATION.cff file was generated with cffinit.
# Visit https://bit.ly/cffinit to generate yours today!
cff-version: 1.2.0
title: BlenderBIM Add-on
message: >-
If you use this software, please cite it using the
metadata from this file.
type: software
authors:
- name: "IfcOpenShell contributors"
repository-code: >-
https://github.com/IfcOpenShell/IfcOpenShell/tree/v0.7.0/src/blenderbim
url: 'https://blenderbim.org/'
repository-artifact: 'https://blenderbim.org/download.html'
abstract: >-
Add-on to Blender providing a graphical native IFC
authoring platform
keywords:
- IFC
- Blender
- NativeIFC
license: GPL-3.0-or-later
commit: 1fb6dac
version: v0.0.230506
date-released: '2023-05-23'
+113 -101
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-476ab50-$(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/
@@ -408,14 +380,16 @@ endif
cd dist/working && . env/bin/activate && $(PIP) install cjio --target=./site-packages
# Provides express rule validation for ifcopenshell.validate
cd dist/working && . env/bin/activate && $(PIP) install pytest --target=./site-packages
cd dist/working/site-packages/ && rm -r *dist-info*
# Provides Brickschema functionality
cd dist/working && . env/bin/activate && $(PIP) install brickschema[persistence] --target=./site-packages
cp -r dist/working/site-packages/* dist/blenderbim/libs/site/packages/
rm -rf dist/working
# 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/
@@ -424,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/
@@ -440,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
@@ -470,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
@@ -485,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)
@@ -500,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
@@ -618,37 +648,11 @@ endif
# Provides Brickschema functionality
mkdir dist/working
cd dist/working && wget https://files.pythonhosted.org/packages/ca/37/8309da9a72407d2f5eb5489c197ac3cfe96ee3ccba1902d2a0ecc7843f92/brickschema-0.5.1.tar.gz
cd dist/working && tar -xzvf brickschema*
# This is an evil hack because we don't want to bundle flask
echo "" > dist/working/brickschema-0.5.1/brickschema/web.py
cd dist/working/brickschema-0.5.1/ && cp -r brickschema ../../blenderbim/libs/site/packages/
# For now lets bundle the latest nightly schema
cd dist/working && wget https://github.com/BrickSchema/Brick/releases/download/nightly/Brick.ttl
cd dist/working && cp Brick.ttl ../blenderbim/bim/schema/Brick.ttl
rm -rf dist/working
# Required by brickschema
mkdir dist/working
cd dist/working && wget https://files.pythonhosted.org/packages/c7/22/37bb938be8e5c20d443b0c9ba0a243573b671b01739efb0a19f81bc5b470/pyshacl-0.17.2.tar.gz
cd dist/working && tar -xzvf pyshacl*
cd dist/working/pyshacl-0.17.2/ && cp -r pyshacl ../../blenderbim/libs/site/packages/
rm -rf dist/working
# Required by brickschema
mkdir dist/working
cd dist/working && wget https://files.pythonhosted.org/packages/d2/a7/be8244688bfcee37c23733ab4fe8e6afa6d4403bd2674a3ae7bd2cecc77b/rdflib-6.0.2.tar.gz
cd dist/working && tar -xzvf rdflib*
cd dist/working/rdflib-6.0.2/ && cp -r rdflib ../../blenderbim/libs/site/packages/
rm -rf dist/working
# Required by brickschema
mkdir dist/working
cd dist/working && wget https://files.pythonhosted.org/packages/7d/c7/208aece36279e4f1236e437119358786e39530ecc1719d4e1afeddba5288/owlrl-6.0.2.tar.gz
cd dist/working && tar -xzvf owlrl*
cd dist/working/owlrl-6.0.2/ && cp -r owlrl ../../blenderbim/libs/site/packages/
rm -rf dist/working
# Required by brickschema
# This is a bit of a dodgy one, it should be provided by setuptools which Blender doesn't ship.
mkdir dist/working
@@ -664,7 +668,15 @@ endif
cp -r dist/working/bpypolyskel-master/bpypolyskel dist/blenderbim/libs/site/packages/
rm -rf dist/working
# Required for Desktop icon and file association
cp -r blenderbim/libs/desktop dist/blenderbim/libs/
# Remove dependencies also bundled with Blender
rm -rf dist/blenderbim/libs/site/packages/numpy
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
+7 -5
View File
@@ -22,19 +22,21 @@ 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):
# Process *.pth in /libs/site/packages to setup globally importable modules
# This is 3 levels deep as required by the static RPATH of ../../ from dependencies taken from Anaconda
site.addsitedir(os.path.join(os.path.dirname(os.path.realpath(__file__)), "libs", "site", "packages"))
# site.addsitedir(os.path.join(os.path.dirname(os.path.realpath(__file__)), "libs", "site", "packages"))
sys.path.insert(0, os.path.join(os.path.dirname(os.path.realpath(__file__)), "libs", "site", "packages"))
import blenderbim.bim
+109 -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,15 +91,18 @@ 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,
operator.SelectIfcFile,
operator.ReloadSelectedIfcFile,
operator.SelectSchemaDir,
operator.FileAssociate,
operator.FileUnassociate,
operator.SelectURIAttribute,
operator.EditBlenderCollection,
operator.BIM_OT_open_webbrowser,
@@ -103,59 +111,83 @@ 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,
prop.GlobalId,
prop.BIMObjectProperties,
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,
# 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)
bpy.types.Material.BIMMaterialProperties = bpy.props.PointerProperty(type=prop.BIMMaterialProperties)
@@ -163,25 +195,55 @@ 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
del bpy.types.Material.BIMObjectProperties
del bpy.types.Material.BIMMaterialProperties
@@ -189,9 +251,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

@@ -0,0 +1,755 @@
{
"Technical": {
"render_type": "VIEWPORT",
"raster_style": {
"bpy.data.worlds[0].color": [
1.0,
1.0,
1.0
],
"scene.render.bake_bias": 0.0010000000474974513,
"scene.render.bake_margin": 16,
"scene.render.bake_margin_type": "ADJACENT_FACES",
"scene.render.bake_samples": 256,
"scene.render.bake_type": "NORMALS",
"scene.render.bake_user_scale": 0.0,
"scene.render.border_max_x": 1.0,
"scene.render.border_max_y": 1.0,
"scene.render.border_min_x": 0.0,
"scene.render.border_min_y": 0.0,
"scene.render.dither_intensity": 1.0,
"scene.render.engine": "BLENDER_WORKBENCH",
"scene.render.film_transparent": false,
"scene.render.filter_size": 1.5,
"scene.render.fps": 24,
"scene.render.fps_base": 1.0,
"scene.render.frame_map_new": 100,
"scene.render.frame_map_old": 100,
"scene.render.hair_subdiv": 0,
"scene.render.hair_type": "STRAND",
"scene.render.line_thickness": 1.0,
"scene.render.line_thickness_mode": "ABSOLUTE",
"scene.render.metadata_input": "SCENE",
"scene.render.motion_blur_shutter": 0.5,
"scene.render.pixel_aspect_x": 1.0,
"scene.render.pixel_aspect_y": 1.0,
"scene.render.preview_pixel_size": "AUTO",
"scene.render.resolution_percentage": 100,
"scene.render.resolution_x": 1000,
"scene.render.resolution_y": 1000,
"scene.render.sequencer_gl_preview": "SOLID",
"scene.render.simplify_child_particles": 1.0,
"scene.render.simplify_child_particles_render": 1.0,
"scene.render.simplify_gpencil": false,
"scene.render.simplify_gpencil_antialiasing": true,
"scene.render.simplify_gpencil_modifier": true,
"scene.render.simplify_gpencil_onplay": false,
"scene.render.simplify_gpencil_shader_fx": true,
"scene.render.simplify_gpencil_tint": true,
"scene.render.simplify_gpencil_view_fill": true,
"scene.render.simplify_shadows": 1.0,
"scene.render.simplify_shadows_render": 1.0,
"scene.render.simplify_subdivision": 6,
"scene.render.simplify_subdivision_render": 6,
"scene.render.simplify_volumes": 1.0,
"scene.render.stamp_font_size": 12,
"scene.render.stamp_note_text": "",
"scene.render.threads": 20,
"scene.render.threads_mode": "AUTO",
"scene.render.use_bake_clear": true,
"scene.render.use_bake_lores_mesh": false,
"scene.render.use_bake_multires": false,
"scene.render.use_bake_selected_to_active": false,
"scene.render.use_bake_user_scale": false,
"scene.render.use_border": false,
"scene.render.use_compositing": true,
"scene.render.use_crop_to_border": false,
"scene.render.use_file_extension": true,
"scene.render.use_freestyle": false,
"scene.render.use_high_quality_normals": false,
"scene.render.use_lock_interface": false,
"scene.render.use_motion_blur": false,
"scene.render.use_multiview": false,
"scene.render.use_overwrite": true,
"scene.render.use_persistent_data": false,
"scene.render.use_placeholder": false,
"scene.render.use_render_cache": false,
"scene.render.use_sequencer": true,
"scene.render.use_sequencer_override_scene_strip": false,
"scene.render.use_simplify": false,
"scene.render.use_single_layer": false,
"scene.render.use_stamp": false,
"scene.render.use_stamp_camera": true,
"scene.render.use_stamp_date": true,
"scene.render.use_stamp_filename": true,
"scene.render.use_stamp_frame": true,
"scene.render.use_stamp_frame_range": false,
"scene.render.use_stamp_hostname": false,
"scene.render.use_stamp_labels": true,
"scene.render.use_stamp_lens": false,
"scene.render.use_stamp_marker": false,
"scene.render.use_stamp_memory": false,
"scene.render.use_stamp_note": false,
"scene.render.use_stamp_render_time": true,
"scene.render.use_stamp_scene": true,
"scene.render.use_stamp_sequencer_strip": false,
"scene.render.use_stamp_time": true,
"scene.render.views_format": "STEREO_3D",
"scene.view_settings.exposure": 0.0,
"scene.view_settings.gamma": 1.0,
"scene.view_settings.look": "None",
"scene.view_settings.use_curve_mapping": false,
"scene.view_settings.view_transform": "Standard",
"scene.display.shading.aov_name": "",
"scene.display.shading.background_color": [
0.0,
0.0,
0.0
],
"scene.display.shading.background_type": "THEME",
"scene.display.shading.cavity_ridge_factor": 1.0,
"scene.display.shading.cavity_type": "BOTH",
"scene.display.shading.cavity_valley_factor": 1.0,
"scene.display.shading.color_type": "SINGLE",
"scene.display.shading.curvature_ridge_factor": 1.0,
"scene.display.shading.curvature_valley_factor": 1.0,
"scene.display.shading.light": "FLAT",
"scene.display.shading.object_outline_color": [
0.0,
0.0,
0.0
],
"scene.display.shading.render_pass": "COMBINED",
"scene.display.shading.shadow_intensity": 0.5,
"scene.display.shading.show_backface_culling": false,
"scene.display.shading.show_cavity": false,
"scene.display.shading.show_object_outline": true,
"scene.display.shading.show_shadows": false,
"scene.display.shading.show_specular_highlight": true,
"scene.display.shading.show_xray": false,
"scene.display.shading.show_xray_wireframe": false,
"scene.display.shading.single_color": [
1.0,
1.0,
1.0
],
"scene.display.shading.studio_light": "Default",
"scene.display.shading.studiolight_background_alpha": 0.0,
"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.use_compositor": "DISABLED",
"scene.display.shading.use_dof": false,
"scene.display.shading.use_scene_lights": false,
"scene.display.shading.use_scene_lights_render": false,
"scene.display.shading.use_scene_world": false,
"scene.display.shading.use_scene_world_render": false,
"scene.display.shading.use_studiolight_view_rotation": true,
"scene.display.shading.use_world_space_lighting": false,
"scene.display.shading.wireframe_color_type": "MATERIAL",
"scene.display.shading.xray_alpha": 0.5,
"scene.display.shading.xray_alpha_wireframe": 0.0,
"scene.display.light_direction": [
0.5,
0.5,
0.5
],
"scene.display.matcap_ssao_attenuation": 1.0,
"scene.display.matcap_ssao_distance": 0.20000000298023224,
"scene.display.matcap_ssao_samples": 16,
"scene.display.render_aa": "8",
"scene.display.shadow_focus": 0.0,
"scene.display.shadow_shift": 0.10000000149011612,
"scene.display.viewport_aa": "FXAA",
"space.overlay.backwire_opacity": 0.5,
"space.overlay.bone_wire_alpha": 1.0,
"space.overlay.display_handle": "SELECTED",
"space.overlay.fade_inactive_alpha": 0.4000000059604645,
"space.overlay.gpencil_fade_layer": 0.5,
"space.overlay.gpencil_fade_objects": 0.5,
"space.overlay.gpencil_grid_opacity": 0.5,
"space.overlay.gpencil_vertex_paint_opacity": 1.0,
"space.overlay.grid_lines": 16,
"space.overlay.grid_scale": 1.0,
"space.overlay.grid_subdivisions": 10,
"space.overlay.normals_constant_screen_size": 7.0,
"space.overlay.normals_length": 0.10000000149011612,
"space.overlay.sculpt_curves_cage_opacity": 0.5,
"space.overlay.sculpt_mode_face_sets_opacity": 1.0,
"space.overlay.sculpt_mode_mask_opacity": 0.75,
"space.overlay.show_annotation": true,
"space.overlay.show_axis_x": false,
"space.overlay.show_axis_y": false,
"space.overlay.show_axis_z": false,
"space.overlay.show_bones": true,
"space.overlay.show_cursor": true,
"space.overlay.show_curve_normals": false,
"space.overlay.show_edge_bevel_weight": true,
"space.overlay.show_edge_crease": true,
"space.overlay.show_edge_seams": true,
"space.overlay.show_edge_sharp": true,
"space.overlay.show_edges": false,
"space.overlay.show_extra_edge_angle": false,
"space.overlay.show_extra_edge_length": false,
"space.overlay.show_extra_face_angle": false,
"space.overlay.show_extra_face_area": false,
"space.overlay.show_extra_indices": false,
"space.overlay.show_extras": true,
"space.overlay.show_face_center": false,
"space.overlay.show_face_normals": false,
"space.overlay.show_face_orientation": false,
"space.overlay.show_faces": true,
"space.overlay.show_fade_inactive": false,
"space.overlay.show_floor": false,
"space.overlay.show_freestyle_edge_marks": true,
"space.overlay.show_freestyle_face_marks": true,
"space.overlay.show_look_dev": false,
"space.overlay.show_motion_paths": true,
"space.overlay.show_object_origins": false,
"space.overlay.show_object_origins_all": false,
"space.overlay.show_occlude_wire": false,
"space.overlay.show_onion_skins": false,
"space.overlay.show_ortho_grid": true,
"space.overlay.show_outline_selected": true,
"space.overlay.show_overlays": true,
"space.overlay.show_paint_wire": false,
"space.overlay.show_relationship_lines": false,
"space.overlay.show_sculpt_curves_cage": true,
"space.overlay.show_sculpt_face_sets": true,
"space.overlay.show_sculpt_mask": true,
"space.overlay.show_split_normals": false,
"space.overlay.show_stats": false,
"space.overlay.show_statvis": false,
"space.overlay.show_text": true,
"space.overlay.show_vertex_normals": false,
"space.overlay.show_viewer_attribute": true,
"space.overlay.show_weight": false,
"space.overlay.show_wireframes": true,
"space.overlay.show_wpaint_contours": false,
"space.overlay.show_xray_bone": false,
"space.overlay.texture_paint_mode_opacity": 1.0,
"space.overlay.use_debug_freeze_view_culling": false,
"space.overlay.use_gpencil_canvas_xray": false,
"space.overlay.use_gpencil_edit_lines": true,
"space.overlay.use_gpencil_fade_gp_objects": false,
"space.overlay.use_gpencil_fade_layers": false,
"space.overlay.use_gpencil_fade_objects": false,
"space.overlay.use_gpencil_grid": false,
"space.overlay.use_gpencil_multiedit_line_only": false,
"space.overlay.use_gpencil_onion_skin": false,
"space.overlay.use_gpencil_show_directions": false,
"space.overlay.use_gpencil_show_material_name": false,
"space.overlay.use_normals_constant_screen_size": false,
"space.overlay.vertex_opacity": 1.0,
"space.overlay.vertex_paint_mode_opacity": 1.0,
"space.overlay.viewer_attribute_opacity": 1.0,
"space.overlay.weight_paint_mode_opacity": 1.0,
"space.overlay.wireframe_opacity": 1.0,
"space.overlay.wireframe_threshold": 0.0,
"space.overlay.xray_alpha_bone": 0.0
}
},
"Shaded": {
"render_type": "VIEWPORT",
"raster_style": {
"bpy.data.worlds[0].color": [
1.0,
1.0,
1.0
],
"scene.render.bake_bias": 0.0010000000474974513,
"scene.render.bake_margin": 16,
"scene.render.bake_margin_type": "ADJACENT_FACES",
"scene.render.bake_samples": 256,
"scene.render.bake_type": "NORMALS",
"scene.render.bake_user_scale": 0.0,
"scene.render.border_max_x": 1.0,
"scene.render.border_max_y": 1.0,
"scene.render.border_min_x": 0.0,
"scene.render.border_min_y": 0.0,
"scene.render.dither_intensity": 1.0,
"scene.render.engine": "BLENDER_WORKBENCH",
"scene.render.film_transparent": false,
"scene.render.filter_size": 1.5,
"scene.render.fps": 24,
"scene.render.fps_base": 1.0,
"scene.render.frame_map_new": 100,
"scene.render.frame_map_old": 100,
"scene.render.hair_subdiv": 0,
"scene.render.hair_type": "STRAND",
"scene.render.line_thickness": 1.0,
"scene.render.line_thickness_mode": "ABSOLUTE",
"scene.render.metadata_input": "SCENE",
"scene.render.motion_blur_shutter": 0.5,
"scene.render.pixel_aspect_x": 1.0,
"scene.render.pixel_aspect_y": 1.0,
"scene.render.preview_pixel_size": "AUTO",
"scene.render.resolution_percentage": 100,
"scene.render.resolution_x": 1000,
"scene.render.resolution_y": 1000,
"scene.render.sequencer_gl_preview": "SOLID",
"scene.render.simplify_child_particles": 1.0,
"scene.render.simplify_child_particles_render": 1.0,
"scene.render.simplify_gpencil": false,
"scene.render.simplify_gpencil_antialiasing": true,
"scene.render.simplify_gpencil_modifier": true,
"scene.render.simplify_gpencil_onplay": false,
"scene.render.simplify_gpencil_shader_fx": true,
"scene.render.simplify_gpencil_tint": true,
"scene.render.simplify_gpencil_view_fill": true,
"scene.render.simplify_shadows": 1.0,
"scene.render.simplify_shadows_render": 1.0,
"scene.render.simplify_subdivision": 6,
"scene.render.simplify_subdivision_render": 6,
"scene.render.simplify_volumes": 1.0,
"scene.render.stamp_font_size": 12,
"scene.render.stamp_note_text": "",
"scene.render.threads": 20,
"scene.render.threads_mode": "AUTO",
"scene.render.use_bake_clear": true,
"scene.render.use_bake_lores_mesh": false,
"scene.render.use_bake_multires": false,
"scene.render.use_bake_selected_to_active": false,
"scene.render.use_bake_user_scale": false,
"scene.render.use_border": false,
"scene.render.use_compositing": true,
"scene.render.use_crop_to_border": false,
"scene.render.use_file_extension": true,
"scene.render.use_freestyle": false,
"scene.render.use_high_quality_normals": false,
"scene.render.use_lock_interface": false,
"scene.render.use_motion_blur": false,
"scene.render.use_multiview": false,
"scene.render.use_overwrite": true,
"scene.render.use_persistent_data": false,
"scene.render.use_placeholder": false,
"scene.render.use_render_cache": false,
"scene.render.use_sequencer": true,
"scene.render.use_sequencer_override_scene_strip": false,
"scene.render.use_simplify": false,
"scene.render.use_single_layer": false,
"scene.render.use_stamp": false,
"scene.render.use_stamp_camera": true,
"scene.render.use_stamp_date": true,
"scene.render.use_stamp_filename": true,
"scene.render.use_stamp_frame": true,
"scene.render.use_stamp_frame_range": false,
"scene.render.use_stamp_hostname": false,
"scene.render.use_stamp_labels": true,
"scene.render.use_stamp_lens": false,
"scene.render.use_stamp_marker": false,
"scene.render.use_stamp_memory": false,
"scene.render.use_stamp_note": false,
"scene.render.use_stamp_render_time": true,
"scene.render.use_stamp_scene": true,
"scene.render.use_stamp_sequencer_strip": false,
"scene.render.use_stamp_time": true,
"scene.render.views_format": "STEREO_3D",
"scene.view_settings.exposure": 0.0,
"scene.view_settings.gamma": 1.0,
"scene.view_settings.look": "None",
"scene.view_settings.use_curve_mapping": false,
"scene.view_settings.view_transform": "Standard",
"scene.display.shading.aov_name": "",
"scene.display.shading.background_color": [
0.0,
0.0,
0.0
],
"scene.display.shading.background_type": "THEME",
"scene.display.shading.cavity_ridge_factor": 1.0,
"scene.display.shading.cavity_type": "BOTH",
"scene.display.shading.cavity_valley_factor": 1.0,
"scene.display.shading.color_type": "MATERIAL",
"scene.display.shading.curvature_ridge_factor": 1.0,
"scene.display.shading.curvature_valley_factor": 1.0,
"scene.display.shading.light": "STUDIO",
"scene.display.shading.object_outline_color": [
0.0,
0.0,
0.0
],
"scene.display.shading.render_pass": "COMBINED",
"scene.display.shading.shadow_intensity": 0.5,
"scene.display.shading.show_backface_culling": false,
"scene.display.shading.show_cavity": true,
"scene.display.shading.show_object_outline": true,
"scene.display.shading.show_shadows": true,
"scene.display.shading.show_specular_highlight": true,
"scene.display.shading.show_xray": false,
"scene.display.shading.show_xray_wireframe": false,
"scene.display.shading.single_color": [
1.0,
1.0,
1.0
],
"scene.display.shading.studio_light": "Default",
"scene.display.shading.studiolight_background_alpha": 0.0,
"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": "RENDERED",
"scene.display.shading.use_compositor": "DISABLED",
"scene.display.shading.use_dof": false,
"scene.display.shading.use_scene_lights": false,
"scene.display.shading.use_scene_lights_render": false,
"scene.display.shading.use_scene_world": false,
"scene.display.shading.use_scene_world_render": false,
"scene.display.shading.use_studiolight_view_rotation": true,
"scene.display.shading.use_world_space_lighting": false,
"scene.display.shading.wireframe_color_type": "MATERIAL",
"scene.display.shading.xray_alpha": 0.5,
"scene.display.shading.xray_alpha_wireframe": 0.0,
"scene.display.light_direction": [
0.5,
0.5,
0.5
],
"scene.display.matcap_ssao_attenuation": 1.0,
"scene.display.matcap_ssao_distance": 0.20000000298023224,
"scene.display.matcap_ssao_samples": 16,
"scene.display.render_aa": "8",
"scene.display.shadow_focus": 0.0,
"scene.display.shadow_shift": 0.10000000149011612,
"scene.display.viewport_aa": "FXAA",
"space.overlay.backwire_opacity": 0.5,
"space.overlay.bone_wire_alpha": 1.0,
"space.overlay.display_handle": "SELECTED",
"space.overlay.fade_inactive_alpha": 0.4000000059604645,
"space.overlay.gpencil_fade_layer": 0.5,
"space.overlay.gpencil_fade_objects": 0.5,
"space.overlay.gpencil_grid_opacity": 0.5,
"space.overlay.gpencil_vertex_paint_opacity": 1.0,
"space.overlay.grid_lines": 16,
"space.overlay.grid_scale": 1.0,
"space.overlay.grid_subdivisions": 10,
"space.overlay.normals_constant_screen_size": 7.0,
"space.overlay.normals_length": 0.10000000149011612,
"space.overlay.sculpt_curves_cage_opacity": 0.5,
"space.overlay.sculpt_mode_face_sets_opacity": 1.0,
"space.overlay.sculpt_mode_mask_opacity": 0.75,
"space.overlay.show_annotation": true,
"space.overlay.show_axis_x": false,
"space.overlay.show_axis_y": false,
"space.overlay.show_axis_z": false,
"space.overlay.show_bones": true,
"space.overlay.show_cursor": true,
"space.overlay.show_curve_normals": false,
"space.overlay.show_edge_bevel_weight": true,
"space.overlay.show_edge_crease": true,
"space.overlay.show_edge_seams": true,
"space.overlay.show_edge_sharp": true,
"space.overlay.show_edges": false,
"space.overlay.show_extra_edge_angle": false,
"space.overlay.show_extra_edge_length": false,
"space.overlay.show_extra_face_angle": false,
"space.overlay.show_extra_face_area": false,
"space.overlay.show_extra_indices": false,
"space.overlay.show_extras": true,
"space.overlay.show_face_center": false,
"space.overlay.show_face_normals": false,
"space.overlay.show_face_orientation": false,
"space.overlay.show_faces": true,
"space.overlay.show_fade_inactive": false,
"space.overlay.show_floor": false,
"space.overlay.show_freestyle_edge_marks": true,
"space.overlay.show_freestyle_face_marks": true,
"space.overlay.show_look_dev": false,
"space.overlay.show_motion_paths": true,
"space.overlay.show_object_origins": false,
"space.overlay.show_object_origins_all": false,
"space.overlay.show_occlude_wire": false,
"space.overlay.show_onion_skins": false,
"space.overlay.show_ortho_grid": true,
"space.overlay.show_outline_selected": true,
"space.overlay.show_overlays": true,
"space.overlay.show_paint_wire": false,
"space.overlay.show_relationship_lines": false,
"space.overlay.show_sculpt_curves_cage": true,
"space.overlay.show_sculpt_face_sets": true,
"space.overlay.show_sculpt_mask": true,
"space.overlay.show_split_normals": false,
"space.overlay.show_stats": false,
"space.overlay.show_statvis": false,
"space.overlay.show_text": true,
"space.overlay.show_vertex_normals": false,
"space.overlay.show_viewer_attribute": true,
"space.overlay.show_weight": false,
"space.overlay.show_wireframes": false,
"space.overlay.show_wpaint_contours": false,
"space.overlay.show_xray_bone": false,
"space.overlay.texture_paint_mode_opacity": 1.0,
"space.overlay.use_debug_freeze_view_culling": false,
"space.overlay.use_gpencil_canvas_xray": false,
"space.overlay.use_gpencil_edit_lines": true,
"space.overlay.use_gpencil_fade_gp_objects": false,
"space.overlay.use_gpencil_fade_layers": false,
"space.overlay.use_gpencil_fade_objects": false,
"space.overlay.use_gpencil_grid": false,
"space.overlay.use_gpencil_multiedit_line_only": false,
"space.overlay.use_gpencil_onion_skin": false,
"space.overlay.use_gpencil_show_directions": false,
"space.overlay.use_gpencil_show_material_name": false,
"space.overlay.use_normals_constant_screen_size": false,
"space.overlay.vertex_opacity": 1.0,
"space.overlay.vertex_paint_mode_opacity": 1.0,
"space.overlay.viewer_attribute_opacity": 1.0,
"space.overlay.weight_paint_mode_opacity": 1.0,
"space.overlay.wireframe_opacity": 1.0,
"space.overlay.wireframe_threshold": 0.0,
"space.overlay.xray_alpha_bone": 0.0
}
},
"Blender Default": {
"render_type": "VIEWPORT",
"raster_style": {
"bpy.data.worlds[0].color": [
0.05087608844041824,
0.05087608844041824,
0.05087608844041824
],
"scene.render.bake_bias": 0.0010000000474974513,
"scene.render.bake_margin": 16,
"scene.render.bake_margin_type": "ADJACENT_FACES",
"scene.render.bake_samples": 256,
"scene.render.bake_type": "NORMALS",
"scene.render.bake_user_scale": 0.0,
"scene.render.border_max_x": 1.0,
"scene.render.border_max_y": 1.0,
"scene.render.border_min_x": 0.0,
"scene.render.border_min_y": 0.0,
"scene.render.dither_intensity": 1.0,
"scene.render.engine": "BLENDER_EEVEE",
"scene.render.film_transparent": false,
"scene.render.filter_size": 1.5,
"scene.render.fps": 24,
"scene.render.fps_base": 1.0,
"scene.render.frame_map_new": 100,
"scene.render.frame_map_old": 100,
"scene.render.hair_subdiv": 0,
"scene.render.hair_type": "STRAND",
"scene.render.line_thickness": 1.0,
"scene.render.line_thickness_mode": "ABSOLUTE",
"scene.render.metadata_input": "SCENE",
"scene.render.motion_blur_shutter": 0.5,
"scene.render.pixel_aspect_x": 1.0,
"scene.render.pixel_aspect_y": 1.0,
"scene.render.preview_pixel_size": "AUTO",
"scene.render.resolution_percentage": 100,
"scene.render.resolution_x": 1000,
"scene.render.resolution_y": 1000,
"scene.render.sequencer_gl_preview": "SOLID",
"scene.render.simplify_child_particles": 1.0,
"scene.render.simplify_child_particles_render": 1.0,
"scene.render.simplify_gpencil": false,
"scene.render.simplify_gpencil_antialiasing": true,
"scene.render.simplify_gpencil_modifier": true,
"scene.render.simplify_gpencil_onplay": false,
"scene.render.simplify_gpencil_shader_fx": true,
"scene.render.simplify_gpencil_tint": true,
"scene.render.simplify_gpencil_view_fill": true,
"scene.render.simplify_shadows": 1.0,
"scene.render.simplify_shadows_render": 1.0,
"scene.render.simplify_subdivision": 6,
"scene.render.simplify_subdivision_render": 6,
"scene.render.simplify_volumes": 1.0,
"scene.render.stamp_font_size": 12,
"scene.render.stamp_note_text": "",
"scene.render.threads": 20,
"scene.render.threads_mode": "AUTO",
"scene.render.use_bake_clear": true,
"scene.render.use_bake_lores_mesh": false,
"scene.render.use_bake_multires": false,
"scene.render.use_bake_selected_to_active": false,
"scene.render.use_bake_user_scale": false,
"scene.render.use_border": false,
"scene.render.use_compositing": true,
"scene.render.use_crop_to_border": false,
"scene.render.use_file_extension": true,
"scene.render.use_freestyle": false,
"scene.render.use_high_quality_normals": false,
"scene.render.use_lock_interface": false,
"scene.render.use_motion_blur": false,
"scene.render.use_multiview": false,
"scene.render.use_overwrite": true,
"scene.render.use_persistent_data": false,
"scene.render.use_placeholder": false,
"scene.render.use_render_cache": false,
"scene.render.use_sequencer": true,
"scene.render.use_sequencer_override_scene_strip": false,
"scene.render.use_simplify": false,
"scene.render.use_single_layer": false,
"scene.render.use_stamp": false,
"scene.render.use_stamp_camera": true,
"scene.render.use_stamp_date": true,
"scene.render.use_stamp_filename": true,
"scene.render.use_stamp_frame": true,
"scene.render.use_stamp_frame_range": false,
"scene.render.use_stamp_hostname": false,
"scene.render.use_stamp_labels": true,
"scene.render.use_stamp_lens": false,
"scene.render.use_stamp_marker": false,
"scene.render.use_stamp_memory": false,
"scene.render.use_stamp_note": false,
"scene.render.use_stamp_render_time": true,
"scene.render.use_stamp_scene": true,
"scene.render.use_stamp_sequencer_strip": false,
"scene.render.use_stamp_time": true,
"scene.render.views_format": "STEREO_3D",
"scene.view_settings.exposure": 0.0,
"scene.view_settings.gamma": 1.0,
"scene.view_settings.look": "None",
"scene.view_settings.use_curve_mapping": false,
"scene.view_settings.view_transform": "Filmic",
"scene.display.shading.aov_name": "",
"scene.display.shading.background_color": [
0.0,
0.0,
0.0
],
"scene.display.shading.background_type": "THEME",
"scene.display.shading.cavity_ridge_factor": 1.0,
"scene.display.shading.cavity_type": "WORLD",
"scene.display.shading.cavity_valley_factor": 1.0,
"scene.display.shading.color_type": "MATERIAL",
"scene.display.shading.curvature_ridge_factor": 0.0,
"scene.display.shading.curvature_valley_factor": 0.0,
"scene.display.shading.light": "STUDIO",
"scene.display.shading.object_outline_color": [
0.0,
0.0,
0.0
],
"scene.display.shading.render_pass": "COMBINED",
"scene.display.shading.shadow_intensity": 0.5,
"scene.display.shading.show_backface_culling": false,
"scene.display.shading.show_cavity": false,
"scene.display.shading.show_object_outline": false,
"scene.display.shading.show_shadows": false,
"scene.display.shading.show_specular_highlight": true,
"scene.display.shading.show_xray": false,
"scene.display.shading.show_xray_wireframe": false,
"scene.display.shading.single_color": [
0.800000011920929,
0.800000011920929,
0.800000011920929
],
"scene.display.shading.studio_light": "Default",
"scene.display.shading.studiolight_background_alpha": 0.0,
"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": "RENDERED",
"scene.display.shading.use_compositor": "DISABLED",
"scene.display.shading.use_dof": false,
"scene.display.shading.use_scene_lights": false,
"scene.display.shading.use_scene_lights_render": false,
"scene.display.shading.use_scene_world": false,
"scene.display.shading.use_scene_world_render": false,
"scene.display.shading.use_studiolight_view_rotation": true,
"scene.display.shading.use_world_space_lighting": false,
"scene.display.shading.wireframe_color_type": "MATERIAL",
"scene.display.shading.xray_alpha": 0.5,
"scene.display.shading.xray_alpha_wireframe": 0.0,
"scene.display.light_direction": [
0.5773502588272095,
0.5773502588272095,
0.5773502588272095
],
"scene.display.matcap_ssao_attenuation": 1.0,
"scene.display.matcap_ssao_distance": 0.20000000298023224,
"scene.display.matcap_ssao_samples": 16,
"scene.display.render_aa": "8",
"scene.display.shadow_focus": 0.0,
"scene.display.shadow_shift": 0.10000000149011612,
"scene.display.viewport_aa": "FXAA",
"space.overlay.backwire_opacity": 0.5,
"space.overlay.bone_wire_alpha": 1.0,
"space.overlay.display_handle": "SELECTED",
"space.overlay.fade_inactive_alpha": 0.4000000059604645,
"space.overlay.gpencil_fade_layer": 0.5,
"space.overlay.gpencil_fade_objects": 0.5,
"space.overlay.gpencil_grid_opacity": 0.5,
"space.overlay.gpencil_vertex_paint_opacity": 1.0,
"space.overlay.grid_lines": 16,
"space.overlay.grid_scale": 1.0,
"space.overlay.grid_subdivisions": 10,
"space.overlay.normals_constant_screen_size": 7.0,
"space.overlay.normals_length": 0.10000000149011612,
"space.overlay.sculpt_curves_cage_opacity": 0.5,
"space.overlay.sculpt_mode_face_sets_opacity": 1.0,
"space.overlay.sculpt_mode_mask_opacity": 0.75,
"space.overlay.show_annotation": true,
"space.overlay.show_axis_x": true,
"space.overlay.show_axis_y": true,
"space.overlay.show_axis_z": false,
"space.overlay.show_bones": true,
"space.overlay.show_cursor": true,
"space.overlay.show_curve_normals": false,
"space.overlay.show_edge_bevel_weight": true,
"space.overlay.show_edge_crease": true,
"space.overlay.show_edge_seams": true,
"space.overlay.show_edge_sharp": true,
"space.overlay.show_edges": false,
"space.overlay.show_extra_edge_angle": false,
"space.overlay.show_extra_edge_length": false,
"space.overlay.show_extra_face_angle": false,
"space.overlay.show_extra_face_area": false,
"space.overlay.show_extra_indices": false,
"space.overlay.show_extras": true,
"space.overlay.show_face_center": false,
"space.overlay.show_face_normals": false,
"space.overlay.show_face_orientation": false,
"space.overlay.show_faces": true,
"space.overlay.show_fade_inactive": false,
"space.overlay.show_floor": true,
"space.overlay.show_freestyle_edge_marks": true,
"space.overlay.show_freestyle_face_marks": true,
"space.overlay.show_look_dev": false,
"space.overlay.show_motion_paths": true,
"space.overlay.show_object_origins": true,
"space.overlay.show_object_origins_all": false,
"space.overlay.show_occlude_wire": false,
"space.overlay.show_onion_skins": false,
"space.overlay.show_ortho_grid": true,
"space.overlay.show_outline_selected": true,
"space.overlay.show_overlays": true,
"space.overlay.show_paint_wire": false,
"space.overlay.show_relationship_lines": true,
"space.overlay.show_sculpt_curves_cage": true,
"space.overlay.show_sculpt_face_sets": true,
"space.overlay.show_sculpt_mask": true,
"space.overlay.show_split_normals": false,
"space.overlay.show_stats": false,
"space.overlay.show_statvis": false,
"space.overlay.show_text": true,
"space.overlay.show_vertex_normals": false,
"space.overlay.show_viewer_attribute": true,
"space.overlay.show_weight": false,
"space.overlay.show_wireframes": false,
"space.overlay.show_wpaint_contours": false,
"space.overlay.show_xray_bone": false,
"space.overlay.texture_paint_mode_opacity": 1.0,
"space.overlay.use_debug_freeze_view_culling": false,
"space.overlay.use_gpencil_canvas_xray": false,
"space.overlay.use_gpencil_edit_lines": true,
"space.overlay.use_gpencil_fade_gp_objects": false,
"space.overlay.use_gpencil_fade_layers": false,
"space.overlay.use_gpencil_fade_objects": false,
"space.overlay.use_gpencil_grid": false,
"space.overlay.use_gpencil_multiedit_line_only": false,
"space.overlay.use_gpencil_onion_skin": false,
"space.overlay.use_gpencil_show_directions": false,
"space.overlay.use_gpencil_show_material_name": false,
"space.overlay.use_normals_constant_screen_size": false,
"space.overlay.vertex_opacity": 1.0,
"space.overlay.vertex_paint_mode_opacity": 1.0,
"space.overlay.viewer_attribute_opacity": 1.0,
"space.overlay.weight_paint_mode_opacity": 1.0,
"space.overlay.wireframe_opacity": 1.0,
"space.overlay.wireframe_threshold": 1.0,
"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

@@ -1,67 +1,55 @@
<link href="jsgantt.css" rel="stylesheet" type="text/css"/>
<link href="../../module/sequence/gantt/main.css" rel="stylesheet" type="text/css"/>
<script src="jsgantt.js" type="text/javascript"></script>
<style>
.gtaskcellwkend,
.gtaskcellcurrent,
.gminorheadingwkend {
background-color: #e1e1e1;
}
.gitemhighlight td {
background-color: #ffdaaa;
}
.gtaskblue {
background: #4281A4;
}
.gtaskred {
background: #C1666B;
}
.gtaskgreen {
background: #48A9A6;
}
.gtaskyellow {
background: #D4B483;
}
.gmainleft {
overflow: visible;
flex: 0 1 auto;
}
</style>
<a href="#print" id="print">Print Mode</a>
<div style="position:relative" class="gantt" id="GanttChartDIV"></div>
<script type="text/javascript">
var g = new JSGantt.GanttChart(document.getElementById('GanttChartDIV'), 'day');
g.setOptions({
vCaptionType: 'Caption', // Set to Show Caption : None,Caption,Resource,Duration,Complete,
vQuarterColWidth: 36,
vDateTaskDisplayFormat: 'day dd month yyyy', // Shown in tool tip box
vDayMajorDateDisplayFormat: 'mon yyyy - Week ww',// Set format to dates in the "Major" header of the "Day" view
vWeekMinorDateDisplayFormat: 'dd mon', // Set format to display dates in the "Minor" header of the "Week" view
vLang: 'en',
vShowTaskInfoLink: 1, // Show link in tool tip (0/1)
vShowEndWeekDate: 0, // Show/Hide the date for the last day of the week in header for daily
vUseSingleCell: 10000, // Set the threshold cell per table row (Helps performance for large data.
vFormatArr: ['Day', 'Week', 'Month', 'Quarter'], // Even with setUseSingleCell using Hour format on such a large chart can cause issues in some browsers,
vShowRes: false, // Disable the resource column.
vShowComp: false, // Disable the completion column.
vShowDur: false, // Disable the duration column, because jsgantt doesn't calculate durations the way we want.
vAdditionalHeaders: {ifcduration: {title: 'Duration'}},
vUseToolTip: false, // Disable tooltips.
vTotalHeight: 900,
});
document.getElementById('print').addEventListener('click', function() {
g.setTotalHeight("");
g.Draw();
});
var json_data = `
{{{json_data}}}
`;
JSGantt.parseJSONString(json_data, g);
g.Draw();
<script type="text/javascript" src="../../module/sequence/gantt/index.js"></script>
<script>
var json_data = `{{{json_data}}}`;
</script>
<div id="options" class="no-print">
<h4>Choose a language:
<select id="lang" class="sweet_button" onchange="set_language(event)">
<option value='cn'>Chinese (cn)</option>
<option value='cs'>Czech (cs)</option>
<option value='nl'>Dutch (Standard)</option>
<option value='en' selected>English (en)</option>
<option value='fr'>French (fr)</option>
<option value='de'>German (de)</option>
<option value='hu'>Hungarian (hu)</option>
<option value='id'>Indonesian (id)</option>
<option value='it'>Italian (it)</option>
<option value='ja'>Japanese (ja)</option>
<option value='pt'>Portuguese (pt)</option>
<option value='ru'>Russian (ru)</option>
<option value='es'>Spanish (es)</option>
<option value='sv'>Swedish (sv)</option>
<option value='tr'>Turkish (tr)</option>
</select>
</h4>
<br>
</div>
<div class="top-right no-print" id="print_options">
<select class="sweet_button" id="print_page_size">
<option value="210,297">A4 Portrait</option>
<option value="297,210">A4 Landscape</option>
<option value="297,420">A3 Portrait</option>
<option value="420,297">A3 Landscape</option>
<option value="420,594">A2 Portrait</option>
<option value="594,420">A2 Landscape</option>
<option value="594,841">A1 Portrait</option>
<option value="841,594">A1 Landscape</option>
<option value="841,1189">A0 Portrait</option>
<option value="1189,841">A0 Landscape</option>
</select>
</div>
<div id="schedule-data" class="no-print"></div>
<div id="schedule-header" style=""></div>
<div style="position:relative" class="gantt" id="GanttChartDIV"></div>
<script>
var data = `{{{data}}}`;
setupPage(data);
create_gantt_chart(json_data)
</script>
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
@@ -5,10 +5,11 @@ FILE_NAME('EPset_Drawing.ifc','2020-01-01T00:00:00',(),(),'EPset_Drawing','EPset
FILE_SCHEMA(('IFC4'));
ENDSEC;
DATA;
#1=IFCPROPERTYSETTEMPLATE('2JhNIvqZrFnAgxfhK0XVQX',$,'EPset_Drawing','',.PSET_OCCURRENCEDRIVEN.,'IfcAnnotation',(#2,#3,#4,#5,#6,#7,#8,#9,#10,#11,#12,#13,#14,#15,#16,#17,#18,#19));
#1=IFCPROPERTYSETTEMPLATE('2JhNIvqZrFnAgxfhK0XVQX',$,'EPset_Drawing','',.PSET_OCCURRENCEDRIVEN.,'IfcAnnotation',(#2,#3,#4,#5,#6,#7,#8,#9,#10,#11,#12,#13,#14,#15,#16,#17,#18,#19,#20,#21));
#2=IFCSIMPLEPROPERTYTEMPLATE('23JavTMk98ZxXhrUEnjAcf',$,'TargetView','',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
#3=IFCSIMPLEPROPERTYTEMPLATE('1yVWUt5H9DAOuu0OaMMLpe',$,'Scale','The scale of this drawing represented as a numerator and denominator, such as 1/100',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
#4=IFCSIMPLEPROPERTYTEMPLATE('3gsuPBtU93b8f0gg1pjkq6',$,'HumanScale','The scale of this drawing in human readable format, such as 1:100',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
#4=IFCSIMPLEPROPERTYTEMPLATE('2T$a4OFsv2LeD5JeBKEV4f',$,'IsNTS','Whether or not the scale is intended to be significant',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
#5=IFCSIMPLEPROPERTYTEMPLATE('0AK5C2UpL4$eaac2LszAx$',$,'HasUnderlay','',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
#6=IFCSIMPLEPROPERTYTEMPLATE('2j2ZEZR8X5tONm7kli5hM6',$,'HasLinework','',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
#7=IFCSIMPLEPROPERTYTEMPLATE('1ttChRysH9UuEX2FeMj5Hu',$,'HasAnnotation','',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
@@ -16,13 +17,15 @@ DATA;
#9=IFCSIMPLEPROPERTYTEMPLATE('10hT_1zrzEbRRKMXYAWvtD',$,'Metadata','',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
#10=IFCSIMPLEPROPERTYTEMPLATE('3Z0BXPSG5CWgtI33ioV7aj',$,'Include','Selector expression to include ifc elements in the drawing',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
#11=IFCSIMPLEPROPERTYTEMPLATE('1RVts_g3PAw98PJA2yL3bO',$,'Exclude','Selector expression to exclude ifc elements in the drawing',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
#12=IFCSIMPLEPROPERTYTEMPLATE('0c1$8NpYDEaBiJrj16jHIo',$,'Stylesheet','',.P_SINGLEVALUE.,'IfcURIReference',$,$,$,$,$,.READWRITE.);
#13=IFCSIMPLEPROPERTYTEMPLATE('3mRF52q81FQB$h4oTh7M45',$,'Markers','',.P_SINGLEVALUE.,'IfcURIReference',$,$,$,$,$,.READWRITE.);
#14=IFCSIMPLEPROPERTYTEMPLATE('1rhr_0N3LDtuORcEJP0KXM',$,'Symbols','',.P_SINGLEVALUE.,'IfcURIReference',$,$,$,$,$,.READWRITE.);
#15=IFCSIMPLEPROPERTYTEMPLATE('2sHDBuW7P4TROy$hL2w7ct',$,'Patterns','',.P_SINGLEVALUE.,'IfcURIReference',$,$,$,$,$,.READWRITE.);
#12=IFCSIMPLEPROPERTYTEMPLATE('0c1$8NpYDEaBiJrj16jHIo',$,'Stylesheet','',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
#13=IFCSIMPLEPROPERTYTEMPLATE('3mRF52q81FQB$h4oTh7M45',$,'Markers','',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
#14=IFCSIMPLEPROPERTYTEMPLATE('1rhr_0N3LDtuORcEJP0KXM',$,'Symbols','',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
#15=IFCSIMPLEPROPERTYTEMPLATE('2sHDBuW7P4TROy$hL2w7ct',$,'Patterns','',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
#16=IFCSIMPLEPROPERTYTEMPLATE('1$xfo9EVb26QLqmPll2_RK',$,'MetricPrecision','',.P_SINGLEVALUE.,'IfcReal',$,$,$,$,$,.READWRITE.);
#17=IFCSIMPLEPROPERTYTEMPLATE('38uAtrp9nD_901NO42zd$7',$,'ImperialPrecision','',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
#18=IFCSIMPLEPROPERTYTEMPLATE('1MX0uffTL6TOvtvEJpxFmk',$,'DecimalPlaces','',.P_SINGLEVALUE.,'IfcInteger',$,$,$,$,$,.READWRITE.);
#19=IFCSIMPLEPROPERTYTEMPLATE('0joEq0Rd10cxweEh0NeHT6',$,'JoinCriteria','Comma separated selection keys which determine what cut objects are to be joined.',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
#20=IFCSIMPLEPROPERTYTEMPLATE('0nYMT3OSj5gArVniCWZRtv',$,'ShadingStyles','',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
#21=IFCSIMPLEPROPERTYTEMPLATE('3VWG22eZXBdQwdKlzMeVQH',$,'CurrentShadingStyle','',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
ENDSEC;
END-ISO-10303-21;
@@ -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;
@@ -18,7 +18,7 @@ DATA;
#11=IFCSIMPLEPROPERTYTEMPLATE('1DtsPn5a9FG8$zXHDDMavY',$,'ShowEndArrow','Display end arrow.',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
#12=IFCSIMPLEPROPERTYTEMPLATE('2$6U0mLI9AiPRWeBabdY3u',$,'EndArrowSymbol','Custom symbol for the end of the section marker arrow. Need to make sure it''s present in "symbols.svg".',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
#13=IFCSIMPLEPROPERTYTEMPLATE('1naFqntIL7igCY7hCaE7kq',$,'HasConnectedSectionLine','Connect or disconnect section markers with line (by default = True).',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
#14=IFCPROPERTYSETTEMPLATE('3V8oZ8YRD3_O7uR5vcUleS',$,'BBIM_Documentation','',.PSET_OCCURRENCEDRIVEN.,'IfcProject',(#15,#16,#17,#18,#19,#20,#21,#22));
#14=IFCPROPERTYSETTEMPLATE('3V8oZ8YRD3_O7uR5vcUleS',$,'BBIM_Documentation','',.PSET_OCCURRENCEDRIVEN.,'IfcProject',(#15,#16,#17,#18,#19,#20,#21,#22,#23));
#15=IFCSIMPLEPROPERTYTEMPLATE('0ulAhgk3v9qfGlDILauR6J',$,'SheetsDir','Default sheets directory',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
#16=IFCSIMPLEPROPERTYTEMPLATE('2yvlVKiQXASucfH40deCvu',$,'LayoutsDir','Default layouts directory',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
#17=IFCSIMPLEPROPERTYTEMPLATE('2kXZqXicL3jRwsOnLM_0ho',$,'TitleblocksDir','Default titleblocks directory',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
@@ -27,8 +27,12 @@ DATA;
#20=IFCSIMPLEPROPERTYTEMPLATE('23tejOrxj859R_eCRp1AFP',$,'MarkersPath','Default markers SVG',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
#21=IFCSIMPLEPROPERTYTEMPLATE('1UDakJ5_f7kBhggNSW4$h5',$,'SymbolsPath','Default symbols SVG',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
#22=IFCSIMPLEPROPERTYTEMPLATE('0d53LEtgLDQxnv__NfgH7i',$,'PatternsPath','Default patterns SVG',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
#23=IFCPROPERTYSETTEMPLATE('0I9merLinF5Ap$aZwaclgm',$,'BBIM_Dimension','',.PSET_OCCURRENCEDRIVEN.,'IfcAnnotation,IfcTypeProduct',(#24,#25));
#24=IFCSIMPLEPROPERTYTEMPLATE('1rL2AbQsXD8RbpoWH5pYOV',$,'ShowDescriptionOnly','Hide the measurement values and show only annotation description',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
#25=IFCSIMPLEPROPERTYTEMPLATE('0SVyOfB0rC2xNfdRYf3XvY',$,'SuppressZeroInches','Suppress 0 inch values in dimension annotation text (for example: 12'' - 0" -> 12'')',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
#23=IFCSIMPLEPROPERTYTEMPLATE('26qFNMv7nCHgU6Jd7Anga5',$,'ShadingStylesPath','Default shading styles',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
#24=IFCPROPERTYSETTEMPLATE('0I9merLinF5Ap$aZwaclgm',$,'BBIM_Dimension','',.PSET_OCCURRENCEDRIVEN.,'IfcAnnotation,IfcTypeProduct',(#25,#26,#27,#28));
#25=IFCSIMPLEPROPERTYTEMPLATE('1rL2AbQsXD8RbpoWH5pYOV',$,'ShowDescriptionOnly','Hide the measurement values and show only annotation description',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
#26=IFCSIMPLEPROPERTYTEMPLATE('0SVyOfB0rC2xNfdRYf3XvY',$,'SuppressZeroInches','Suppress 0 inch values in dimension annotation text (for example: 12'' - 0" -> 12'')',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
#27=IFCSIMPLEPROPERTYTEMPLATE('2bUmj458PBqPAtUoI3MXsb',$,'TextPrefix','Text to add before annotation measurement value',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
#28=IFCSIMPLEPROPERTYTEMPLATE('0bnzttUb9BPuN597uNTXOE',$,'TextSuffix','Text to add after annotation measurement value',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.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.
+2 -3
View File
@@ -44,9 +44,8 @@ class IfcExporter:
self.file = IfcStore.get_file()
self.set_header()
IfcStore.update_cache()
if bpy.context.scene.BIMProjectProperties.is_authoring:
self.sync_all_objects()
self.sync_edited_objects()
self.sync_all_objects()
self.sync_edited_objects()
extension = self.ifc_export_settings.output_file.split(".")[-1].lower()
if extension == "ifczip":
with tempfile.TemporaryDirectory() as unzipped_path:
+191 -109
View File
@@ -16,25 +16,26 @@
# 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
import ifcopenshell.api.owner.settings
import blenderbim.tool as tool
import blenderbim.core.owner as core_owner
from blenderbim.bim.module.drawing.prop import RasterStyleProperty
from bpy.app.handlers import persistent
from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.module.owner.prop import get_user_person, get_user_organisation
from blenderbim.bim.module.model.data import AuthoringData
from mathutils import Vector
from math import cos, degrees
cwd = os.path.dirname(os.path.realpath(__file__))
global_subscription_owner = object()
def mode_callback(obj, data):
if not bpy.context.scene.BIMProjectProperties.is_authoring:
return
objects = bpy.context.selected_objects
if bpy.context.active_object:
objects += [bpy.context.active_object]
@@ -53,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:
@@ -73,21 +76,13 @@ 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 element.is_a("IfcSpatialStructureElement") or (hasattr(element, "IsDecomposedBy") and element.IsDecomposedBy):
collection = obj.users_collection[0]
collection.name = obj.name
if element.is_a("IfcGrid"):
axis_obj = IfcStore.get_element(element.UAxes[0].id())
axis_collection = axis_obj.users_collection[0]
grid_collection = None
for collection in bpy.data.collections:
if axis_collection.name in collection.children.keys():
grid_collection = collection
break
if grid_collection:
grid_collection.name = obj.name
if obj.BIMObjectProperties.collection:
obj.BIMObjectProperties.collection.name = obj.name
element.Name = "/".join(obj.name.split("/")[1:])
refresh_ui_data()
@@ -99,6 +94,53 @@ def color_callback(obj, data):
def active_object_callback():
refresh_ui_data()
update_bim_tool_props()
def update_bim_tool_props():
"""update BIM Tools props (such as extrusion_depth, length and x_angle) when active object changes"""
obj = bpy.context.active_object
# bunch of checks to see if we're in a valid state
if not obj:
return
mode = bpy.context.mode
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:
return
representation = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
if not representation:
return
extrusion = tool.Model.get_extrusion(representation)
if not extrusion:
return
def get_x_angle(extrusion):
x, y, z = extrusion.ExtrudedDirection.DirectionRatios
x_angle = Vector((0, 1)).angle_signed(Vector((y, z)))
return x_angle
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
props = bpy.context.scene.BIMModelProperties
if not AuthoringData.is_loaded:
AuthoringData.load()
if AuthoringData.data["active_material_usage"] == "LAYER2":
x_angle = get_x_angle(extrusion)
axis = tool.Model.get_wall_axis(obj)["reference"]
props.extrusion_depth = extrusion.Depth * si_conversion * cos(x_angle)
props.length = (axis[1] - axis[0]).length
props.x_angle = x_angle
elif AuthoringData.data["active_material_usage"] == "LAYER3":
x_angle = get_x_angle(extrusion)
props.x_angle = x_angle
elif AuthoringData.data["active_material_usage"] == "PROFILE":
props.extrusion_depth = extrusion.Depth * si_conversion
def active_material_index_callback(obj, data):
@@ -133,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
@@ -160,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):
@@ -224,82 +251,137 @@ def get_application_version():
)
def viewport_shading_changed_callback(area):
shading = area.spaces.active.shading.type
if shading == "RENDERED":
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(
key=active_object_key, owner=global_subscription_owner, args=(), notify=active_object_callback
)
# subscribe to changes in viewport shading mode
# NOTE: couldn't find a way to make it work for new areas too
# it starts working for them after blender restart though
for screen in bpy.data.screens:
for area in screen.areas:
if area.type != "VIEW_3D":
continue
shading = area.spaces.active.shading
key = shading.path_resolve("type", False)
bpy.msgbus.subscribe_rna(
key=key, owner=global_subscription_owner, args=(area,), notify=viewport_shading_changed_callback
)
ifcopenshell.api.owner.settings.get_user = lambda ifc: core_owner.get_user(tool.Owner)
ifcopenshell.api.owner.settings.get_application = get_application
# TODO: Move to drawing module
if len(bpy.context.scene.DocProperties.drawing_styles) == 0:
drawing_style = bpy.context.scene.DocProperties.drawing_styles.add()
drawing_style.name = "Blender Default"
drawing_style.render_type = "DEFAULT"
bpy.ops.bim.save_drawing_style(index="0")
AuthoringData.type_thumbnails = {}
drawing_style = bpy.context.scene.DocProperties.drawing_styles.add()
drawing_style.name = "Technical"
drawing_style.render_type = "VIEWPORT"
drawing_style.raster_style = json.dumps(
{
RasterStyleProperty.WORLD_COLOR.value: (1, 1, 1),
RasterStyleProperty.RENDER_ENGINE.value: "BLENDER_WORKBENCH",
RasterStyleProperty.RENDER_TRANSPARENT.value: False,
RasterStyleProperty.SHADING_SHOW_OBJECT_OUTLINE.value: True,
RasterStyleProperty.SHADING_SHOW_CAVITY.value: False,
RasterStyleProperty.SHADING_CAVITY_TYPE.value: "BOTH",
RasterStyleProperty.SHADING_CURVATURE_RIDGE_FACTOR.value: 1,
RasterStyleProperty.SHADING_CURVATURE_VALLEY_FACTOR.value: 1,
RasterStyleProperty.VIEW_TRANSFORM.value: "Standard",
RasterStyleProperty.SHADING_LIGHT.value: "FLAT",
RasterStyleProperty.SHADING_COLOR_TYPE.value: "SINGLE",
RasterStyleProperty.SHADING_SINGLE_COLOR.value: (1, 1, 1),
RasterStyleProperty.SHADING_SHOW_SHADOWS.value: False,
RasterStyleProperty.SHADING_SHADOW_INTENSITY.value: 0.5,
RasterStyleProperty.DISPLAY_LIGHT_DIRECTION.value: (0.5, 0.5, 0.5),
RasterStyleProperty.VIEW_USE_CURVE_MAPPING.value: False,
RasterStyleProperty.OVERLAY_SHOW_WIREFRAMES.value: True,
RasterStyleProperty.OVERLAY_WIREFRAME_THRESHOLD.value: 0,
RasterStyleProperty.OVERLAY_SHOW_FLOOR.value: False,
RasterStyleProperty.OVERLAY_SHOW_AXIS_X.value: False,
RasterStyleProperty.OVERLAY_SHOW_AXIS_Y.value: False,
RasterStyleProperty.OVERLAY_SHOW_AXIS_Z.value: False,
RasterStyleProperty.OVERLAY_SHOW_OBJECT_ORIGINS.value: False,
RasterStyleProperty.OVERLAY_SHOW_RELATIONSHIP_LINES.value: False,
}
)
drawing_style = bpy.context.scene.DocProperties.drawing_styles.add()
drawing_style.name = "Shaded"
drawing_style.render_type = "VIEWPORT"
drawing_style.raster_style = json.dumps(
{
RasterStyleProperty.WORLD_COLOR.value: (1, 1, 1),
RasterStyleProperty.RENDER_ENGINE.value: "BLENDER_WORKBENCH",
RasterStyleProperty.RENDER_TRANSPARENT.value: False,
RasterStyleProperty.SHADING_SHOW_OBJECT_OUTLINE.value: True,
RasterStyleProperty.SHADING_SHOW_CAVITY.value: True,
RasterStyleProperty.SHADING_CAVITY_TYPE.value: "BOTH",
RasterStyleProperty.SHADING_CURVATURE_RIDGE_FACTOR.value: 1,
RasterStyleProperty.SHADING_CURVATURE_VALLEY_FACTOR.value: 1,
RasterStyleProperty.VIEW_TRANSFORM.value: "Standard",
RasterStyleProperty.SHADING_LIGHT.value: "STUDIO",
RasterStyleProperty.SHADING_COLOR_TYPE.value: "MATERIAL",
RasterStyleProperty.SHADING_SINGLE_COLOR.value: (1, 1, 1),
RasterStyleProperty.SHADING_SHOW_SHADOWS.value: True,
RasterStyleProperty.SHADING_SHADOW_INTENSITY.value: 0.5,
RasterStyleProperty.DISPLAY_LIGHT_DIRECTION.value: (0.5, 0.5, 0.5),
RasterStyleProperty.VIEW_USE_CURVE_MAPPING.value: False,
RasterStyleProperty.OVERLAY_SHOW_WIREFRAMES.value: False,
RasterStyleProperty.OVERLAY_WIREFRAME_THRESHOLD.value: 0,
RasterStyleProperty.OVERLAY_SHOW_FLOOR.value: False,
RasterStyleProperty.OVERLAY_SHOW_AXIS_X.value: False,
RasterStyleProperty.OVERLAY_SHOW_AXIS_Y.value: False,
RasterStyleProperty.OVERLAY_SHOW_AXIS_Z.value: False,
RasterStyleProperty.OVERLAY_SHOW_OBJECT_ORIGINS.value: False,
RasterStyleProperty.OVERLAY_SHOW_RELATIONSHIP_LINES.value: False,
}
)
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)
+240 -306
View File
@@ -36,7 +36,7 @@ import ifcopenshell.util.geolocation
import blenderbim.tool as tool
from itertools import chain, accumulate
from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.module.drawing.prop import get_diagram_scales
from blenderbim.bim.module.drawing.prop import ANNOTATION_TYPES_DATA
class FileCopy(threading.Thread):
@@ -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"]
]
@@ -178,11 +227,16 @@ class IfcImporter:
self.settings.set_deflection_tolerance(self.ifc_import_settings.deflection_tolerance)
self.settings.set_angular_tolerance(self.ifc_import_settings.angular_tolerance)
self.settings.set(self.settings.STRICT_TOLERANCE, True)
self.settings_curve = ifcopenshell.geom.settings()
self.settings_curve.set_deflection_tolerance(self.ifc_import_settings.deflection_tolerance)
self.settings_curve.set_angular_tolerance(self.ifc_import_settings.angular_tolerance)
self.settings_curve.set(self.settings_curve.STRICT_TOLERANCE, True)
self.settings_curve.set(self.settings_curve.INCLUDE_CURVES, True)
self.settings_native = ifcopenshell.geom.settings()
self.settings_native.set(self.settings_native.INCLUDE_CURVES, True)
self.settings_2d = ifcopenshell.geom.settings()
self.settings_2d.set(self.settings_2d.INCLUDE_CURVES, True)
self.settings_2d.set(self.settings.STRICT_TOLERANCE, True)
self.settings_2d.set(self.settings_2d.STRICT_TOLERANCE, True)
self.project = None
self.has_existing_project = False
self.collections = {}
@@ -322,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:
@@ -342,7 +402,8 @@ class IfcImporter:
self.element_types = set(self.file.by_type("IfcTypeProduct"))
if self.ifc_import_settings.has_filter and self.ifc_import_settings.should_filter_spatial_elements:
self.spatial_elements = self.get_spatial_elements_filtered_by_elements(self.elements)
filtered_elements = self.elements | set(self.file.by_type("IfcGrid"))
self.spatial_elements = self.get_spatial_elements_filtered_by_elements(filtered_elements)
else:
if self.file.schema == "IFC2X3":
self.spatial_elements = set(self.file.by_type("IfcSpatialStructureElement"))
@@ -356,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("IfcContext"):
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)
@@ -376,7 +439,7 @@ class IfcImporter:
representations = self.get_transformed_body_representations(element.Representation.Representations)
# Single swept disk solids (e.g. rebar) are better natively represented as beveled curves
if self.is_native_swept_disk_solid(representations):
if self.is_native_swept_disk_solid(element, representations):
self.native_data[element.GlobalId] = {
"representations": representations,
"representation": self.get_body_representation(element.Representation.Representations),
@@ -404,16 +467,20 @@ class IfcImporter:
}
return True
def is_native_swept_disk_solid(self, representations):
def is_native_swept_disk_solid(self, element, representations):
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.Blender.Modifier.is_railing(element):
return False
return True
elif len(items) and ( # See #2508 why we accommodate for invalid IFCs here
items[0].is_a("IfcSweptDiskSolid")
and len({i.is_a() for i in items}) == 1
and len({i.Radius for i in items}) == 1
):
if tool.Blender.Modifier.is_railing(element):
return False
return True
return False
@@ -568,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
@@ -613,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)
@@ -669,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)
@@ -682,29 +754,87 @@ 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 Plans, 3. Point clouds, 4. No representation
# If an element has a representation that doesn't follow 1, 2, or 3, it will not show by default.
# 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_curve_products(elements)
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_products(self, products):
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
results = set()
if not products:
return results
if self.ifc_import_settings.should_use_cpu_multiprocessing:
iterator = ifcopenshell.geom.iterator(
self.settings, self.file, multiprocessing.cpu_count(), include=products
)
iterator = ifcopenshell.geom.iterator(settings, self.file, multiprocessing.cpu_count(), include=products)
else:
iterator = ifcopenshell.geom.iterator(self.settings, self.file, include=products)
iterator = ifcopenshell.geom.iterator(settings, self.file, include=products)
if self.ifc_import_settings.should_cache:
cache = IfcStore.get_cache()
if cache:
@@ -771,10 +901,10 @@ class IfcImporter:
self.structural_collection.children.link(self.structural_connection_collection)
self.project["blender"].children.link(self.structural_collection)
self.create_curve_products(self.file.by_type("IfcStructuralCurveMember"))
self.create_curve_products(self.file.by_type("IfcStructuralCurveConnection"))
self.create_curve_products(self.file.by_type("IfcStructuralSurfaceMember"))
self.create_curve_products(self.file.by_type("IfcStructuralSurfaceConnection"))
self.create_products(self.file.by_type("IfcStructuralCurveMember"), settings=self.settings_2d)
self.create_products(self.file.by_type("IfcStructuralCurveConnection"), settings=self.settings_2d)
self.create_products(self.file.by_type("IfcStructuralSurfaceMember"), settings=self.settings_2d)
self.create_products(self.file.by_type("IfcStructuralSurfaceConnection"), settings=self.settings_2d)
self.create_structural_point_connections()
def create_structural_point_connections(self):
@@ -811,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":
@@ -840,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"):
@@ -856,46 +986,13 @@ 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))
self.link_element(product, obj)
return product
def create_curve_products(self, products):
results = set()
if not products:
return results
if self.ifc_import_settings.should_use_cpu_multiprocessing:
iterator = ifcopenshell.geom.iterator(
self.settings_2d, self.file, multiprocessing.cpu_count(), include=products
)
else:
iterator = ifcopenshell.geom.iterator(self.settings_2d, self.file, include=products)
if self.ifc_import_settings.should_cache:
cache = IfcStore.get_cache()
if cache:
iterator.set_cache(cache)
valid_file = iterator.initialize()
if not valid_file:
return results
checkpoint = time.time()
total = 0
while True:
total += 1
if total % 250 == 0:
print("{} elements processed in {:.2f}s ...".format(total, time.time() - checkpoint))
checkpoint = time.time()
shape = iterator.get()
if shape:
product = self.file.by_id(shape.id)
self.create_product(product, shape)
results.add(product)
if not iterator.next():
break
print("Done creating geometry")
return results
def create_product(self, element, shape=None, mesh=None):
if element is None:
return
@@ -1295,13 +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.remove_doubles(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):
@@ -1333,39 +1438,43 @@ class IfcImporter:
bpy.context.scene.unit_settings.length_unit = "FEET"
elif unit.is_a("IfcNamedUnit") and unit.UnitType == "AREAUNIT":
name = unit.Name if unit.is_a("IfcSIUnit") else unit.Name.lower()
bpy.context.scene.BIMProperties.area_unit = "{}{}".format(
unit.Prefix + "/" if hasattr(unit, "Prefix") and unit.Prefix else "", name
)
try:
bpy.context.scene.BIMProperties.area_unit = "{}{}".format(
unit.Prefix + "/" if hasattr(unit, "Prefix") and unit.Prefix else "", name
)
except: # Probably an invalid unit.
bpy.context.scene.BIMProperties.area_unit = "SQUARE_METRE"
elif unit.is_a("IfcNamedUnit") and unit.UnitType == "VOLUMEUNIT":
name = unit.Name if unit.is_a("IfcSIUnit") else unit.Name.lower()
bpy.context.scene.BIMProperties.volume_unit = "{}{}".format(
unit.Prefix + "/" if hasattr(unit, "Prefix") and unit.Prefix else "", name
)
try:
bpy.context.scene.BIMProperties.volume_unit = "{}{}".format(
unit.Prefix + "/" if hasattr(unit, "Prefix") and unit.Prefix else "", name
)
except: # Probably an invalid unit.
bpy.context.scene.BIMProperties.volume_unit = "CUBIC_METRE"
def create_project(self):
project = self.file.by_type("IfcProject")[0]
self.project = {"ifc": project}
obj = tool.Ifc.get_object(project)
if obj:
self.project["blender"] = obj.users_collection[0]
self.project["blender"] = obj.BIMObjectProperties.collection
self.has_existing_project = True
return
self.project["blender"] = bpy.data.collections.new(
"{}/{}".format(self.project["ifc"].is_a(), self.project["ifc"].Name)
)
obj = self.create_product(self.project["ifc"])
if obj:
self.project["blender"].objects.link(obj)
self.project["blender"].objects.link(obj)
self.project["blender"].BIMCollectionProperties.obj = obj
obj.BIMObjectProperties.collection = self.project["blender"]
def create_collections(self):
if self.ifc_import_settings.collection_mode == "DECOMPOSITION":
self.create_decomposition_collections()
elif self.ifc_import_settings.collection_mode == "SPATIAL_DECOMPOSITION":
self.create_spatial_decomposition_collections()
def create_decomposition_collections(self):
self.create_spatial_decomposition_collections()
self.create_aggregate_collections()
if self.ifc_import_settings.collection_mode == "DECOMPOSITION":
self.create_aggregate_collections()
elif self.ifc_import_settings.collection_mode == "SPATIAL_DECOMPOSITION":
pass
def create_spatial_decomposition_collections(self):
for rel_aggregate in self.project["ifc"].IsDecomposedBy or []:
@@ -1377,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:
@@ -1408,7 +1498,7 @@ class IfcImporter:
obj = tool.Ifc.get_object(element)
if obj:
is_existing = True
collection = obj.users_collection[0]
collection = obj.BIMObjectProperties.collection
self.collections[element.GlobalId] = collection
if not is_existing:
collection = bpy.data.collections.new(tool.Loader.get_name(element))
@@ -1494,183 +1584,18 @@ class IfcImporter:
if not blender_material:
name = style.Name or str(style.id())
blender_material = bpy.data.materials.new(name)
blender_material.use_fake_user = True
self.link_element(style, blender_material)
blender_material.BIMMaterialProperties.ifc_style_id = style.id()
self.material_creator.styles[style.id()] = blender_material
rendering_style = None
texture_style = None
for surface_style in style.Styles:
if surface_style.is_a() == "IfcSurfaceStyleShading":
self.create_surface_style_shading(blender_material, surface_style)
elif surface_style.is_a("IfcSurfaceStyleRendering"):
rendering_style = surface_style
self.create_surface_style_rendering(blender_material, surface_style)
elif surface_style.is_a("IfcSurfaceStyleWithTextures"):
texture_style = surface_style
if rendering_style and texture_style:
self.create_surface_style_with_textures(blender_material, rendering_style, texture_style)
tool.Style.record_shading(blender_material)
def create_surface_style_shading(self, blender_material, surface_style):
alpha = 1.0
# Transparency was added in IFC4
if hasattr(surface_style, "Transparency") and surface_style.Transparency:
alpha = 1 - surface_style.Transparency
blender_material.diffuse_color = (
surface_style.SurfaceColour.Red,
surface_style.SurfaceColour.Green,
surface_style.SurfaceColour.Blue,
alpha,
)
def create_surface_style_rendering(self, blender_material, surface_style):
self.create_surface_style_shading(blender_material, surface_style)
if surface_style.ReflectanceMethod in ["PHYSICAL", "NOTDEFINED"]:
blender_material.use_nodes = True
bsdf = blender_material.node_tree.nodes["Principled BSDF"]
if surface_style.DiffuseColour:
if surface_style.DiffuseColour.is_a("IfcColourRgb"):
bsdf.inputs["Base Color"].default_value = (
surface_style.DiffuseColour.Red,
surface_style.DiffuseColour.Green,
surface_style.DiffuseColour.Blue,
1,
)
elif surface_style.DiffuseColour.is_a("IfcNormalisedRatioMeasure"):
bsdf.inputs["Base Color"].default_value = (
surface_style.SurfaceColour.Red * surface_style.DiffuseColour.wrappedValue,
surface_style.SurfaceColour.Green * surface_style.DiffuseColour.wrappedValue,
surface_style.SurfaceColour.Blue * surface_style.DiffuseColour.wrappedValue,
1,
)
if surface_style.SpecularColour and surface_style.SpecularColour.is_a("IfcNormalisedRatioMeasure"):
bsdf.inputs["Metallic"].default_value = surface_style.SpecularColour.wrappedValue
if surface_style.SpecularHighlight and surface_style.SpecularHighlight.is_a("IfcSpecularRoughness"):
bsdf.inputs["Roughness"].default_value = surface_style.SpecularHighlight.wrappedValue
if hasattr(surface_style, "Transparency") and surface_style.Transparency:
bsdf.inputs["Alpha"].default_value = 1 - surface_style.Transparency
blender_material.blend_method = "BLEND"
elif surface_style.ReflectanceMethod == "FLAT":
blender_material.use_nodes = True
output = {n.type: n for n in blender_material.node_tree.nodes}.get("OUTPUT_MATERIAL", None)
bsdf = blender_material.node_tree.nodes["Principled BSDF"]
mix = blender_material.node_tree.nodes.new(type="ShaderNodeMixShader")
mix.location = bsdf.location
blender_material.node_tree.links.new(mix.outputs[0], output.inputs["Surface"])
blender_material.node_tree.nodes.remove(bsdf)
lightpath = blender_material.node_tree.nodes.new(type="ShaderNodeLightPath")
lightpath.location = mix.location - mathutils.Vector((200, -200))
blender_material.node_tree.links.new(lightpath.outputs[0], mix.inputs[0])
bsdf = blender_material.node_tree.nodes.new(type="ShaderNodeBsdfTransparent")
bsdf.location = mix.location - mathutils.Vector((200, 0))
blender_material.node_tree.links.new(bsdf.outputs[0], mix.inputs[1])
rgb = blender_material.node_tree.nodes.new(type="ShaderNodeRGB")
rgb.location = mix.location - mathutils.Vector((200, 200))
blender_material.node_tree.links.new(rgb.outputs[0], mix.inputs[2])
if surface_style.DiffuseColour and surface_style.DiffuseColour.is_a("IfcColourRgb"):
rgb.outputs[0].default_value = (
surface_style.DiffuseColour.Red,
surface_style.DiffuseColour.Green,
surface_style.DiffuseColour.Blue,
1,
)
def create_surface_style_with_textures(self, blender_material, rendering_style, texture_style):
for texture in texture_style.Textures:
mode = getattr(texture, "Mode", None)
node = None
if texture.is_a("IfcImageTexture"):
image_url = texture.URLReference
if not os.path.abspath(texture.URLReference) and tool.Ifc.get_path():
image_url = os.path.join(os.path.dirname(tool.Ifc.get_path()), texture.URLReference)
if rendering_style.ReflectanceMethod in ["PHYSICAL", "NOTDEFINED"]:
bsdf = blender_material.node_tree.nodes["Principled BSDF"]
if mode == "NORMAL":
normalmap = blender_material.node_tree.nodes.new(type="ShaderNodeNormalMap")
normalmap.location = bsdf.location - mathutils.Vector((200, 0))
blender_material.node_tree.links.new(normalmap.outputs[0], bsdf.inputs["Normal"])
node = blender_material.node_tree.nodes.new(type="ShaderNodeTexImage")
node.location = normalmap.location - mathutils.Vector((200, 0))
image = bpy.data.images.load(image_url)
image.colorspace_settings.name = "Non-Color"
node.image = image
blender_material.node_tree.links.new(node.outputs[0], normalmap.inputs["Color"])
elif mode == "EMISSIVE":
output = {n.type: n for n in blender_material.node_tree.nodes}.get("OUTPUT_MATERIAL", None)
add = blender_material.node_tree.nodes.new(type="ShaderNodeAddShader")
add.location = bsdf.location + mathutils.Vector((200, 0))
blender_material.node_tree.links.new(bsdf.outputs[0], add.inputs[1])
blender_material.node_tree.links.new(add.outputs[0], output.inputs[0])
emission = blender_material.node_tree.nodes.new(type="ShaderNodeEmission")
emission.location = add.location - mathutils.Vector((200, 0))
blender_material.node_tree.links.new(emission.outputs[0], add.inputs[0])
node = blender_material.node_tree.nodes.new(type="ShaderNodeTexImage")
node.location = emission.location - mathutils.Vector((200, 0))
image = bpy.data.images.load(image_url)
node.image = image
blender_material.node_tree.links.new(node.outputs[0], emission.inputs[0])
elif mode == "METALLICROUGHNESS":
separate = blender_material.node_tree.nodes.new(type="ShaderNodeSeparateRGB")
separate.location = bsdf.location - mathutils.Vector((200, 0))
blender_material.node_tree.links.new(separate.outputs[1], bsdf.inputs["Roughness"])
blender_material.node_tree.links.new(separate.outputs[2], bsdf.inputs["Metallic"])
node = blender_material.node_tree.nodes.new(type="ShaderNodeTexImage")
node.location = separate.location - mathutils.Vector((200, 0))
image = bpy.data.images.load(image_url)
image.colorspace_settings.name = "Non-Color"
node.image = image
blender_material.node_tree.links.new(node.outputs[0], separate.inputs[0])
elif mode == "OCCLUSION":
# TODO work out how to implement glTF settings here
# https://docs.blender.org/manual/en/dev/addons/import_export/scene_gltf2.html
pass
elif mode == "DIFFUSE":
node = blender_material.node_tree.nodes.new(type="ShaderNodeTexImage")
node.location = bsdf.location - mathutils.Vector((400, 0))
image = bpy.data.images.load(image_url)
node.image = image
blender_material.node_tree.links.new(node.outputs[0], bsdf.inputs["Base Color"])
blender_material.node_tree.links.new(node.outputs[1], bsdf.inputs["Alpha"])
blender_material.blend_method = "BLEND"
elif rendering_style.ReflectanceMethod == "FLAT":
bsdf = blender_material.node_tree.nodes["Mix Shader"]
if mode == "EMISSIVE":
node = blender_material.node_tree.nodes.new(type="ShaderNodeTexImage")
node.location = bsdf.location - mathutils.Vector((200, 0))
image = bpy.data.images.load(image_url)
node.image = image
blender_material.node_tree.links.new(node.outputs[0], bsdf.inputs[2])
if node and getattr(texture, "IsMappedBy", None):
coordinates = texture.IsMappedBy[0]
coord = blender_material.node_tree.nodes.new(type="ShaderNodeTexCoord")
coord.location = node.location - mathutils.Vector((200, 0))
if coordinates.is_a("IfcTextureCoordinateGenerator") and coordinates.Mode == "COORD":
blender_material.node_tree.links.new(coord.outputs["Generated"], node.inputs["Vector"])
elif coordinates.is_a("IfcTextureCoordinateGenerator") and coordinates.Mode == "COORD-EYE":
blender_material.node_tree.links.new(coord.outputs["Camera"], node.inputs["Vector"])
else:
blender_material.node_tree.links.new(coord.outputs["UV"], node.inputs["Vector"])
style_elements = tool.Style.get_style_elements(blender_material)
if tool.Style.has_blender_external_style(style_elements):
blender_material.BIMStyleProperties.active_style_type = "External"
else:
blender_material.BIMStyleProperties.active_style_type = "Shading"
def place_objects_in_collections(self):
for ifc_definition_id, obj in self.added_data.items():
@@ -1690,6 +1615,8 @@ class IfcImporter:
return
elif element.GlobalId in self.collections:
collection = self.collections[element.GlobalId]
collection.BIMCollectionProperties.obj = obj
obj.BIMObjectProperties.collection = collection
collection.name = obj.name
return collection.objects.link(obj)
elif getattr(element, "Decomposes", None):
@@ -1704,7 +1631,10 @@ class IfcImporter:
elif element.is_a("IfcGridAxis"):
return
elif element.GlobalId in self.collections:
return self.collections[element.GlobalId].objects.link(obj)
collection = self.collections[element.GlobalId]
collection.BIMCollectionProperties.obj = obj
obj.BIMObjectProperties.collection = collection
return collection.objects.link(obj)
elif element.is_a("IfcTypeObject"):
return self.type_collection.objects.link(obj)
elif element.is_a("IfcStructuralMember"):
@@ -1731,14 +1661,8 @@ class IfcImporter:
bpy.context.scene.collection.objects.link(obj)
def is_curve_annotation(self, element):
return element.ObjectType in [
"DIMENSION",
"EQUAL_DIMENSION",
"PLAN_LEVEL",
"SECTION_LEVEL",
"STAIR_ARROW",
"TEXT_LEADER",
]
object_type = element.ObjectType
return object_type in ANNOTATION_TYPES_DATA and ANNOTATION_TYPES_DATA[object_type][3] == "curve"
def get_drawing_group(self, element):
for rel in element.HasAssignments or []:
@@ -1746,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
@@ -1896,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)
@@ -1903,6 +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.update()
else:
e = geometry.edges
@@ -1979,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
@@ -2008,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
@@ -40,16 +40,31 @@ class BIM_OT_assign_object(bpy.types.Operator, Operator):
bl_label = "Assign Object"
bl_options = {"REGISTER", "UNDO"}
relating_object: bpy.props.IntProperty()
related_object: bpy.props.IntProperty()
def _execute(self, context):
core.assign_object(
tool.Ifc,
tool.Aggregate,
tool.Collector,
relating_obj=tool.Ifc.get_object(tool.Ifc.get().by_id(self.relating_object)),
related_obj=tool.Ifc.get_object(tool.Ifc.get().by_id(self.related_object)),
)
relating_obj = None
if self.relating_object:
relating_obj = tool.Ifc.get_object(tool.Ifc.get().by_id(self.relating_object))
elif context.active_object:
relating_obj = context.active_object
if not relating_obj:
return
for obj in bpy.context.selected_objects:
if obj == relating_obj:
continue
element = tool.Ifc.get_entity(obj)
if not element:
continue
result = core.assign_object(
tool.Ifc,
tool.Aggregate,
tool.Collector,
relating_obj=relating_obj,
related_obj=obj,
)
if not result:
self.report({"ERROR"}, f" Cannot aggregate {obj.name} to {relating_obj.name}")
class BIM_OT_unassign_object(bpy.types.Operator, Operator):
@@ -148,10 +163,7 @@ class BIM_OT_add_aggregate(bpy.types.Operator, tool.Ifc.Operator):
core.assign_object(tool.Ifc, tool.Aggregate, tool.Collector, relating_obj=aggregate, related_obj=obj)
def create_aggregate(self, context, ifc_class):
aggregate_collection = bpy.data.collections.new(f"{ifc_class}/Assembly")
context.scene.collection.children.link(aggregate_collection)
aggregate = bpy.data.objects.new("Assembly", None)
aggregate_collection.objects.link(aggregate)
bpy.ops.bim.assign_class(obj=aggregate.name, ifc_class=ifc_class)
return aggregate
@@ -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):
@@ -56,7 +56,6 @@ class BIM_PT_aggregate(Panel):
if props.relating_object:
op = row.operator("bim.assign_object", icon="CHECKMARK", text="")
op.relating_object = props.relating_object.BIMObjectProperties.ifc_definition_id
op.related_object = context.active_object.BIMObjectProperties.ifc_definition_id
row.operator("bim.disable_editing_aggregate", icon="CANCEL", text="")
else:
row = layout.row(align=True)
@@ -60,6 +60,8 @@ class EnableEditingAttributes(bpy.types.Operator):
new.data_type = "string"
new.ifc_class = data["type"]
new.string_value = "" if new.is_null else json.dumps(data[name])
blenderbim.bim.helper.add_attribute_description(new)
new.description += " The degrees, minutes and seconds should follow this format : [12,34,56]"
blenderbim.bim.helper.import_attributes2(
tool.Ifc.get().by_id(oprops.ifc_definition_id), props.attributes, callback=callback
@@ -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
@@ -20,28 +20,37 @@ import bpy
from . import ui, operator, prop
classes = (
operator.AddBoundary,
operator.ColourByRelatedBuildingElement,
operator.DisableEditingBoundary,
operator.DisableEditingBoundaryGeometry,
operator.EditBoundaryAttributes,
operator.EditBoundaryGeometry,
operator.EnableEditingBoundary,
operator.EnableEditingBoundaryGeometry,
operator.HideBoundaries,
operator.LoadBoundary,
operator.LoadProjectSpaceBoundaries,
operator.LoadSpaceBoundaries,
operator.LoadBoundary,
operator.SelectRelatedElementBoundaries,
operator.SelectProjectBoundaries,
operator.SelectRelatedElementBoundaries,
operator.SelectRelatedElementTypeBoundaries,
operator.SelectSpaceBoundaries,
operator.ShowBoundaries,
operator.UpdateBoundaryGeometry,
ui.BIM_PT_Boundary,
ui.BIM_PT_SceneBoundaries,
ui.BIM_PT_SpaceBoundaries,
prop.BIMBoundaryProperties,
prop.BIMObjectBoundaryProperties,
)
def register():
bpy.types.Object.bim_boundary_properties = bpy.props.PointerProperty(type=prop.BIMBoundaryProperties)
bpy.types.Scene.BIMBoundaryProperties = bpy.props.PointerProperty(type=prop.BIMBoundaryProperties)
bpy.types.Object.bim_boundary_properties = bpy.props.PointerProperty(type=prop.BIMObjectBoundaryProperties)
def unregister():
del bpy.types.Scene.BIMBoundaryProperties
del bpy.types.Object.bim_boundary_properties
@@ -0,0 +1,112 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2023 Dion Moult <dion@thinkmoult.com>
#
# This file is part of BlenderBIM Add-on.
#
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# BlenderBIM Add-on is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import gpu
import bmesh
import blenderbim.tool as tool
from bpy.types import SpaceView3D
from mathutils import Vector
from gpu_extras.batch import batch_for_shader
class BoundaryDecorator:
installed = None
@classmethod
def install(cls, context):
if cls.installed:
cls.uninstall()
handler = cls()
cls.installed = SpaceView3D.draw_handler_add(handler, (context,), "WINDOW", "POST_VIEW")
@classmethod
def uninstall(cls):
try:
SpaceView3D.draw_handler_remove(cls.installed, "WINDOW")
except ValueError:
pass
cls.installed = None
def draw_batch(self, shader_type, content_pos, color, indices=None):
shader = self.line_shader if shader_type == "LINES" else self.shader
batch = batch_for_shader(shader, shader_type, {"pos": content_pos}, indices=indices)
shader.uniform_float("color", color)
batch.draw(shader)
def __call__(self, context):
self.addon_prefs = context.preferences.addons["blenderbim"].preferences
selected_elements_color = self.addon_prefs.decorator_color_selected
unselected_elements_color = self.addon_prefs.decorator_color_unselected
special_elements_color = self.addon_prefs.decorator_color_special
def transparent_color(color, alpha=0.1):
color = [i for i in color]
color[3] = alpha
return color
gpu.state.point_size_set(6)
gpu.state.blend_set("ALPHA")
self.line_shader = gpu.shader.from_builtin("3D_POLYLINE_UNIFORM_COLOR")
self.line_shader.bind() # required to be able to change uniforms of the shader
# POLYLINE_UNIFORM_COLOR specific uniforms
self.line_shader.uniform_float("viewportSize", (context.region.width, context.region.height))
self.line_shader.uniform_float("lineWidth", 2.0)
# general shader
self.shader = gpu.shader.from_builtin("3D_UNIFORM_COLOR")
selected_vertices = []
selected_edges = []
selected_tris = []
unselected_vertices = []
unselected_edges = []
unselected_tris = []
for boundary in context.scene.BIMBoundaryProperties.boundaries:
obj = boundary.obj
if not obj or not obj.data: # A boundary may not have data if it has no connection geometry
continue
if obj.mode == "EDIT":
continue # A profile decorator or something else is used here.
else:
bm = bmesh.new()
bm.from_mesh(obj.data)
obj.data.calc_loop_triangles()
if obj.select_get():
offset = len(selected_vertices)
selected_vertices.extend([tuple(obj.matrix_world @ v.co) for v in bm.verts])
selected_edges.extend([tuple([v.index + offset for v in e.verts]) for e in bm.edges])
selected_tris.extend([tuple([i + offset for i in t.vertices]) for t in obj.data.loop_triangles])
else:
offset = len(unselected_vertices)
unselected_vertices.extend([tuple(obj.matrix_world @ v.co) for v in bm.verts])
unselected_edges.extend([tuple([v.index + offset for v in e.verts]) for e in bm.edges])
unselected_tris.extend([tuple([i + offset for i in t.vertices]) for t in obj.data.loop_triangles])
if obj.mode != "EDIT":
bm.free()
if unselected_edges:
self.draw_batch("LINES", unselected_vertices, special_elements_color, unselected_edges)
self.draw_batch("TRIS", unselected_vertices, transparent_color(special_elements_color), unselected_tris)
if selected_edges:
self.draw_batch("LINES", selected_vertices, selected_elements_color, selected_edges)
self.draw_batch("TRIS", selected_vertices, transparent_color(selected_elements_color), selected_tris)
@@ -18,18 +18,28 @@
import bpy
import bmesh
import mathutils
import logging
import shapely
import mathutils
import numpy as np
import ifcopenshell.api
import ifcopenshell.util.placement
import ifcopenshell.util.unit
import ifcopenshell.util.shape
import ifcopenshell.util.element
import ifcopenshell.util.placement
import ifcopenshell.util.representation
import blenderbim.tool as tool
from blenderbim.bim.ifc import IfcStore
import blenderbim.bim.import_ifc as import_ifc
from math import pi, inf
from mathutils import Vector, Matrix
from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.module.model.decorator import ProfileDecorator
from blenderbim.bim.module.boundary.decorator import BoundaryDecorator
import blenderbim.core
def get_boundaries_collection(blender_space):
space_collection = bpy.data.collections.get(blender_space.name, blender_space.users_collection[0])
space_collection = blender_space.BIMObjectProperties.collection
collection_name = f"Boundaries/{blender_space.BIMObjectProperties.ifc_definition_id}"
boundaries_collection = space_collection.children.get(collection_name)
if not boundaries_collection:
@@ -38,6 +48,20 @@ def get_boundaries_collection(blender_space):
return boundaries_collection
def disable_editing_boundary_geometry(context):
ProfileDecorator.uninstall()
bpy.ops.object.mode_set(mode="OBJECT")
obj = context.active_object
element = tool.Ifc.get_entity(obj)
old_mesh = obj.data
loader = Loader()
obj.data = loader.create_mesh(element)
tool.Geometry.delete_data(old_mesh)
return {"FINISHED"}
class Loader:
def __init__(self):
self.ifc_file = None
@@ -79,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
@@ -376,3 +398,357 @@ class UpdateBoundaryGeometry(bpy.types.Operator):
settings = tool.Boundary.get_assign_connection_geometry_settings(context.active_object)
ifcopenshell.api.run("boundary.assign_connection_geometry", tool.Ifc.get(), **settings)
return {"FINISHED"}
class EnableEditingBoundaryGeometry(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.enable_editing_boundary_geometry"
bl_label = "Enable Editing Boundary Geometry"
bl_options = {"REGISTER", "UNDO"}
@classmethod
def poll(cls, context):
return context.selected_objects
def _execute(self, context):
self.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
obj = context.active_object
element = tool.Ifc.get_entity(obj)
if element.ConnectionGeometry.is_a("IfcConnectionSurfaceGeometry"):
surface = element.ConnectionGeometry.SurfaceOnRelatingElement
tool.Model.import_surface(surface, obj)
bpy.ops.object.mode_set(mode="EDIT")
ProfileDecorator.install(context, exit_edit_mode_callback=lambda: disable_editing_boundary_geometry(context))
if not bpy.app.background:
bpy.ops.wm.tool_set_by_id(tool.Blender.get_viewport_context(), name="bim.cad_tool")
return {"FINISHED"}
class EditBoundaryGeometry(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.edit_boundary_geometry"
bl_label = "Edit Boundary Geometry"
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
self.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
ProfileDecorator.uninstall()
bpy.ops.object.mode_set(mode="OBJECT")
obj = context.active_object
element = tool.Ifc.get_entity(obj)
if element.ConnectionGeometry.is_a("IfcConnectionSurfaceGeometry"):
surface = tool.Model.export_surface(obj)
if not surface:
def msg(self, context):
self.layout.label(text="INVALID PROFILE")
bpy.context.window_manager.popup_menu(msg, title="Error", icon="ERROR")
ProfileDecorator.install(
context, exit_edit_mode_callback=lambda: disable_editing_boundary_geometry(context)
)
bpy.ops.object.mode_set(mode="EDIT")
return
old_surface = element.ConnectionGeometry.SurfaceOnRelatingElement
for inverse in tool.Ifc.get().get_inverse(old_surface):
ifcopenshell.util.element.replace_attribute(inverse, old_surface, surface)
ifcopenshell.util.element.remove_deep2(tool.Ifc.get(), old_surface)
old_mesh = obj.data
loader = Loader()
obj.data = loader.create_mesh(element)
tool.Geometry.delete_data(old_mesh)
class DisableEditingBoundaryGeometry(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.disable_editing_boundary_geometry"
bl_label = "Disable Editing Boundary Geometry"
bl_options = {"REGISTER", "UNDO"}
@classmethod
def poll(cls, context):
return context.selected_objects
def _execute(self, context):
return disable_editing_boundary_geometry(context)
class ShowBoundaries(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.show_boundaries"
bl_label = "Show Boundaries"
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
props = bpy.context.scene.BIMBoundaryProperties
loader = Loader()
for obj in context.selected_objects:
element = tool.Ifc.get_entity(obj)
if not element or not getattr(element, "BoundedBy", None):
continue
if tool.Ifc.is_moved(obj):
blenderbim.core.geometry.edit_object_placement(tool.Ifc, tool.Geometry, tool.Surveyor, obj=obj)
element = tool.Ifc.get_entity(obj)
for rel in element.BoundedBy or []:
boundary_obj = loader.load_boundary(rel, obj)
tool.Boundary.decorate_boundary(boundary_obj)
BoundaryDecorator.install(bpy.context)
return {"FINISHED"}
class HideBoundaries(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.hide_boundaries"
bl_label = "Hide Boundaries"
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
to_delete = set()
spaces = set()
for obj in context.selected_objects:
element = tool.Ifc.get_entity(obj)
if not element:
continue
if element.is_a("IfcSpace"):
spaces.add(element)
elif element.is_a("IfcRelSpaceBoundary"):
spaces.add(element.RelatingSpace)
for element in spaces:
for boundary in element.BoundedBy or []:
boundary_obj = tool.Ifc.get_object(boundary)
if boundary_obj:
to_delete.add(boundary_obj)
for boundary_obj in to_delete:
tool.Ifc.unlink(obj=boundary_obj)
bpy.data.objects.remove(boundary_obj)
context.scene.BIMBoundaryProperties.boundaries.clear()
return {"FINISHED"}
class AddBoundary(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.add_boundary"
bl_label = "Add Boundary"
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
relating_space = None
related_building_element = None
relating_space_obj = None
related_building_element_obj = None
objs = context.selected_objects
if len(objs) == 2:
# The user may select two objects, a space and its related building element
for obj in objs:
element = tool.Ifc.get_entity(obj)
if not element:
continue
if element.is_a("IfcSpace"):
relating_space = element
relating_space_obj = obj
else:
related_building_element = element
related_building_element_obj = obj
elif len(objs) == 1:
# Optionally the user may select just the space, and the building element shall be auto-detected
def msg(self, context):
self.layout.label(text="NO ACTIVE STOREY")
element = tool.Ifc.get_entity(objs[0])
if element.is_a("IfcSpace"):
relating_space = element
relating_space_obj = objs[0]
target = bpy.context.scene.cursor.location
collection = context.view_layer.active_layer_collection.collection
collection_obj = bpy.data.objects.get(collection.name)
if not collection_obj:
bpy.context.window_manager.popup_menu(msg, title="Error", icon="ERROR")
return
spatial_element = tool.Ifc.get_entity(collection_obj)
if not spatial_element:
bpy.context.window_manager.popup_menu(msg, title="Error", icon="ERROR")
return
for subelement in ifcopenshell.util.element.get_decomposition(spatial_element):
if not (subelement.is_a("IfcWall") or subelement.is_a("IfcSlab")):
continue
obj = tool.Ifc.get_object(subelement)
if obj:
raycast = obj.closest_point_on_mesh(obj.matrix_world.inverted() @ target, distance=0.1)
if raycast[0]:
related_building_element = subelement
related_building_element_obj = obj
break
if not relating_space or not related_building_element:
return
bm = bmesh.new()
bm.from_mesh(relating_space_obj.data)
bmesh.ops.dissolve_limit(bm, angle_limit=pi * 2 / 360, verts=bm.verts, edges=bm.edges)
target_distance = inf
target_face = None
for face in bm.faces:
centroid = relating_space_obj.matrix_world @ face.calc_center_median()
raycast = related_building_element_obj.closest_point_on_mesh(
related_building_element_obj.matrix_world.inverted() @ centroid, distance=1
)
if raycast[0]:
distance = (related_building_element_obj.matrix_world @ raycast[1] - centroid).length
if distance < target_distance:
target_face = face
target_distance = distance
if not target_face:
return
parent_boundary = tool.Ifc.run("root.create_entity", ifc_class=context.scene.BIMModelProperties.boundary_class)
# Is this right? Or should I use loop?
target_face_verts = [v.co.copy() for v in target_face.verts]
target_face_matrix = self.get_face_matrix(*[v.copy() for v in target_face_verts[0:3]])
target_face_matrix_i = target_face_matrix.inverted()
target_face_polygon = shapely.Polygon([tuple((target_face_matrix_i @ v).xy) for v in target_face_verts])
related_building_element_polygon = self.get_flattened_polygon(
related_building_element, relating_space_obj, target_face_matrix_i
)
gross_boundary_polygon = target_face_polygon.intersection(related_building_element_polygon)
if type(gross_boundary_polygon) == shapely.GeometryCollection:
for geom in gross_boundary_polygon.geoms:
if type(geom) == shapely.Polygon:
gross_boundary_polygon = geom
break
# The gross boundary polygon may not be a true gross boundary since it
# may have openings already removed, such as in IFC4 Reference View. So
# we cheat by using the exterior boundary to mean "gross". Later, we
# can use this to check whether or not the opening is relevant to our
# space.
exterior_boundary_polygon = shapely.Polygon(gross_boundary_polygon.exterior.coords)
net_boundary_polygon = shapely.Polygon(gross_boundary_polygon)
inner_boundaries = []
for rel in getattr(related_building_element, "HasOpenings", []):
opening = rel.RelatedOpeningElement
filling = None
if opening.HasFillings:
filling = opening.HasFillings[0].RelatedBuildingElement
opening_polygon = self.get_flattened_polygon(opening, relating_space_obj, target_face_matrix_i)
net_boundary_polygon = net_boundary_polygon.difference(opening_polygon)
# Only openings that are projected onto our exterior boundary are relevant.
if opening_polygon.intersection(exterior_boundary_polygon).area == 0:
continue
connection_geometry = self.create_connection_geometry_from_polygon(opening_polygon, target_face_matrix)
boundary = tool.Ifc.run("root.create_entity", ifc_class=context.scene.BIMModelProperties.boundary_class)
boundary.RelatingSpace = relating_space
boundary.RelatedBuildingElement = filling or related_building_element
boundary.ConnectionGeometry = connection_geometry
boundary.PhysicalOrVirtualBoundary = "PHYSICAL" if filling else "VIRTUAL"
boundary.InternalOrExternalBoundary = "INTERNAL"
if boundary.is_a("IfcRelSpaceBoundary2ndLevel"):
boundary.ParentBoundary = parent_boundary
connection_geometry = self.create_connection_geometry_from_polygon(net_boundary_polygon, target_face_matrix)
parent_boundary.RelatingSpace = relating_space
parent_boundary.RelatedBuildingElement = related_building_element
parent_boundary.ConnectionGeometry = connection_geometry
parent_boundary.PhysicalOrVirtualBoundary = "PHYSICAL"
parent_boundary.InternalOrExternalBoundary = "INTERNAL"
bpy.ops.bim.show_boundaries()
obj = tool.Ifc.get_object(parent_boundary)
obj.select_set(True)
def get_face_matrix(self, p1, p2, p3):
edge1 = p2 - p1
edge2 = p3 - p1
normal = edge1.cross(edge2)
z_axis = normal.normalized()
x_axis = p2 - p1
x_axis.normalize()
y_axis = z_axis.cross(x_axis)
mat = Matrix()
mat.col[0][:3] = x_axis
mat.col[1][:3] = y_axis
mat.col[2][:3] = z_axis
mat.translation = p1
return mat
def get_flattened_polygon(self, element, relating_space_obj, target_face_matrix_i):
obj = tool.Ifc.get_object(element)
if obj and tool.Ifc.is_moved(obj):
blenderbim.core.geometry.edit_object_placement(tool.Ifc, tool.Geometry, tool.Surveyor, obj=obj)
space_matrix_i = relating_space_obj.matrix_world.inverted()
settings = ifcopenshell.geom.settings()
if not element.is_a("IfcOpeningElement"):
settings.set(settings.DISABLE_OPENING_SUBTRACTIONS, True)
settings.set(settings.STRICT_TOLERANCE, True)
# geometry = ifcopenshell.geom.create_shape(settings, body)
shape = ifcopenshell.geom.create_shape(settings, element)
m = shape.transformation.matrix.data
mat = Matrix(([m[0], m[3], m[6], m[9]], [m[1], m[4], m[7], m[10]], [m[2], m[5], m[8], m[11]], [0, 0, 0, 1]))
verts = [space_matrix_i @ mat @ Vector(v) for v in ifcopenshell.util.shape.get_vertices(shape.geometry)]
faces = ifcopenshell.util.shape.get_faces(shape.geometry)
polygons = []
for face in faces:
polygon = shapely.Polygon([tuple((target_face_matrix_i @ verts[vi]).xy) for vi in face])
polygons.append(polygon)
return shapely.ops.unary_union(polygons)
def create_connection_geometry_from_polygon(self, polygon, target_face_matrix):
surface = self.export_surface(polygon, target_face_matrix)
return tool.Ifc.get().createIfcConnectionSurfaceGeometry(surface)
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.translation
self.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
tool.Model.unit_scale = self.unit_scale
surface = tool.Ifc.get().createIfcCurveBoundedPlane()
surface.BasisSurface = tool.Ifc.get().createIfcPlane(tool.Ifc.get().createIfcAxis2Placement3D(
tool.Ifc.get().createIfcCartesianPoint([o / self.unit_scale for o in p1]),
tool.Ifc.get().createIfcDirection([float(o) for o in z_axis]),
tool.Ifc.get().createIfcDirection([float(o) for o in x_axis]),
))
if tool.Ifc.get().schema != "IFC2X3":
points = [tool.Model.convert_si_to_unit(list(co)) for co in polygon.exterior.coords]
point_list = tool.Ifc.get().createIfcCartesianPointList2D(points)
outer_boundary = tool.Ifc.get().createIfcIndexedPolyCurve(point_list, None, False)
inner_boundaries = []
for interior in polygon.interiors:
points = [tool.Model.convert_si_to_unit(list(co)) for co in interior.coords]
point_list = tool.Ifc.get().createIfcCartesianPointList2D(points)
inner_boundaries.append(tool.Ifc.get().createIfcIndexedPolyCurve(point_list, None, False))
else:
pass # TODO
surface.OuterBoundary = outer_boundary
surface.InnerBoundaries = inner_boundaries
return surface
@@ -18,6 +18,7 @@
import bpy
from bpy.types import PropertyGroup
from blenderbim.bim.prop import ObjProperty
from bpy.props import (
PointerProperty,
StringProperty,
@@ -52,9 +53,13 @@ def element_filter(self, object):
return False
class BIMBoundaryProperties(PropertyGroup):
class BIMObjectBoundaryProperties(PropertyGroup):
is_editing: BoolProperty(name="Is Editing")
relating_space: PointerProperty(name="RelatingSpace", type=bpy.types.Object, poll=space_filter)
related_building_element: PointerProperty(name="RelatedBuildingElement", type=bpy.types.Object, poll=element_filter)
parent_boundary: PointerProperty(name="ParentBoundary", type=bpy.types.Object, poll=boundary_filter)
corresponding_boundary: PointerProperty(name="CorrespondingBoundary", type=bpy.types.Object, poll=boundary_filter)
class BIMBoundaryProperties(PropertyGroup):
boundaries: bpy.props.CollectionProperty(type=ObjProperty)
@@ -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,10 @@ classes = (
operator.RewindBrickClass,
operator.ViewBrickClass,
operator.ViewBrickItem,
operator.SerializeBrick,
operator.AddBrickNamespace,
operator.SetBrickListRoot,
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"}
@@ -39,7 +41,11 @@ class LoadBrickProject(bpy.types.Operator, Operator):
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)
@@ -51,9 +57,10 @@ class ViewBrickClass(bpy.types.Operator, Operator):
bl_label = "View Brick Class"
bl_options = {"REGISTER", "UNDO"}
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):
@@ -61,18 +68,20 @@ class ViewBrickItem(bpy.types.Operator, Operator):
bl_label = "View Brick Item"
bl_options = {"REGISTER", "UNDO"}
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"}
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):
@@ -124,24 +133,31 @@ 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"}
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,22 +174,46 @@ 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"}
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"}
split_screen: bpy.props.BoolProperty(name="Split Screen", default=False, options={"HIDDEN"})
def _execute(self, context):
core.refresh_brick_viewer(tool.Brick)
core.refresh_brick_viewer(tool.Brick, split_screen=self.split_screen)
class RemoveBrick(bpy.types.Operator, Operator):
@@ -189,3 +229,69 @@ 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"
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 SetBrickListRoot(bpy.types.Operator, Operator):
bl_idname = "bim.set_brick_list_root"
bl_label = "Set Brick View Type"
bl_options = {"REGISTER", "UNDO"}
split_screen: bpy.props.BoolProperty(name="Split Screen", default=False, options={"HIDDEN"})
def _execute(self, context):
if self.split_screen:
root = context.scene.BIMBrickProperties.split_screen_brick_list_root
else:
root = context.scene.BIMBrickProperties.brick_list_root
core.set_brick_list_root(tool.Brick, brick_root=root, split_screen=self.split_screen)
class RemoveBrickRelation(bpy.types.Operator, Operator):
bl_idname = "bim.remove_brick_relation"
bl_label = "Remove Relation"
bl_options = {"REGISTER", "UNDO"}
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,7 @@ from bpy.props import (
FloatVectorProperty,
CollectionProperty,
)
from blenderbim.tool.brick import BrickStore
def update_active_brick_index(self, context):
BrickschemaData.is_loaded = False
@@ -43,15 +43,26 @@ 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
class Brick(PropertyGroup):
@@ -67,5 +78,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)
# 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)
+142 -23
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:
@@ -41,39 +42,154 @@ class BIM_PT_brickschema(Panel):
row.operator("bim.load_brick_project", text="Load Project")
return
if BrickStore.path:
row = self.layout.row(align=True)
row.label(text=BrickStore.path, icon="FILEBROWSER")
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")
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.operator("bim.add_brick", text="", icon="ADD")
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.alignment = "RIGHT"
row.operator("bim.add_brick_feed", text="", icon="PLUGIN")
row.operator("bim.remove_brick", text="", icon="X")
row.label(text="Create Entity:")
self.layout.template_list("BIM_UL_bricks", "", self.props, "bricks", self.props, "active_brick_index")
row = self.layout.row(align=True)
row.prop(data=self.props, property="brick_entity_create_type", text="")
for attribute in BrickschemaData.data["attributes"]:
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.operator("bim.refresh_brick_viewer", text="", icon="FILE_REFRESH")
row = self.layout.row(align=True)
col = row.column()
col.alignment = "RIGHT"
row.prop(data=self.props, property="split_screen_toggled", text="", icon="WINDOW")
grid = self.layout.grid_flow(even_columns=True)
grid1 = grid.column(align=True)
row = grid1.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.prop(data=self.props, property="set_list_root_toggled", text="", icon="OUTLINER")
row.label(text=self.props.active_brick_class)
if self.props.set_list_root_toggled:
row = grid1.row(align=True)
op = row.operator("bim.set_brick_list_root", text="Set View")
op.split_screen = False
row.prop(data=self.props, property="brick_list_root", text="")
row = grid1.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:
grid2 = grid.column(align=True)
row = grid2.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 = grid2.row(align=True)
op = row.operator("bim.set_brick_list_root", text="Set View")
op.split_screen = True
row.prop(data=self.props, property="split_screen_brick_list_root", text="")
row = grid2.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 +238,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)
@@ -33,7 +33,6 @@ class CadTool(WorkSpaceTool):
bl_description = "Gives you CAD authoring related superpowers"
bl_icon = os.path.join(os.path.dirname(__file__), "ops.authoring.cad")
bl_widget = None
# https://docs.blender.org/api/current/bpy.types.KeyMapItems.html
bl_keymap = tool.Blender.get_default_selection_keypmap() + (
("bim.cad_hotkey", {"type": "C", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_C")]}),
("bim.cad_hotkey", {"type": "E", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_E")]}),
@@ -46,7 +45,7 @@ class CadTool(WorkSpaceTool):
("bim.cad_hotkey", {"type": "X", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_X")]}),
)
def draw_settings(context, layout, tool):
def draw_settings(context, layout, workspace_tool):
obj = context.active_object
if not obj or not obj.data:
return
@@ -54,14 +53,19 @@ class CadTool(WorkSpaceTool):
row = layout.row(align=True)
row.label(text="", icon="EVENT_SHIFT")
row.label(text="", icon="EVENT_Q")
if obj.BIMObjectProperties.ifc_definition_id:
row.operator("bim.edit_extrusion_profile", text="Save Profile")
row.operator("bim.align_view_to_profile", text="", icon="AXIS_FRONT")
row.operator("bim.disable_editing_extrusion_profile", text="", icon="CANCEL")
else:
row.operator("bim.edit_arbitrary_profile", text="Save Profile")
row.operator("bim.align_view_to_profile", text="", icon="AXIS_FRONT")
row.operator("bim.disable_editing_arbitrary_profile", text="", icon="CANCEL")
element = tool.Ifc.get_entity(obj)
if element:
if element.is_a("IfcProfileDef"):
row.operator("bim.edit_arbitrary_profile", text="Save Profile")
row.operator("bim.align_view_to_profile", text="", icon="AXIS_FRONT")
row.operator("bim.disable_editing_arbitrary_profile", text="", icon="CANCEL")
elif element.is_a("IfcRelSpaceBoundary"):
row.operator("bim.edit_boundary_geometry", text="Save Profile")
row.operator("bim.disable_editing_boundary_geometry", text="", icon="CANCEL")
else:
row.operator("bim.edit_extrusion_profile", text="Save Profile")
row.operator("bim.align_view_to_profile", text="", icon="AXIS_FRONT")
row.operator("bim.disable_editing_extrusion_profile", text="", icon="CANCEL")
row = layout.row(align=True)
row.label(text="", icon="EVENT_SHIFT")
@@ -133,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)
@@ -225,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")
@@ -255,35 +257,23 @@ class CadHotkey(bpy.types.Operator):
bpy.ops.bim.cad_offset(distance=self.props.distance)
def hotkey_S_Q(self):
if tool.Ifc.get_entity(bpy.context.active_object):
if bpy.context.active_object.data.BIMMeshProperties.subshape_type == "PROFILE":
element = tool.Ifc.get_entity(bpy.context.active_object)
if bpy.context.active_object.data.BIMMeshProperties.subshape_type == "PROFILE":
if element.is_a("IfcProfileDef"):
bpy.ops.bim.edit_arbitrary_profile()
elif element.is_a("IfcRelSpaceBoundary"):
bpy.ops.bim.edit_boundary_geometry()
else:
bpy.ops.bim.edit_extrusion_profile()
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()
else:
bpy.ops.bim.edit_arbitrary_profile()
elif bpy.context.active_object.data.BIMMeshProperties.subshape_type == "AXIS":
bpy.ops.bim.edit_extrusion_axis()
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"
@@ -76,6 +76,8 @@ classes = (
operator.LoadCostItemTaskQuantities,
operator.LoadCostItemResourceQuantities,
operator.ChangeParentCostItem,
operator.CopyCostItem,
operator.AddCurrency,
prop.CostItem,
prop.CostItemQuantity,
prop.CostItemType,
@@ -46,9 +46,16 @@ class CostSchedulesData:
"cost_quantities": cls.cost_quantities(),
"cost_values": cls.cost_values(),
"quantity_types": cls.quantity_types(),
"currency": cls.currency(),
}
cls.is_loaded = True
@classmethod
def currency(cls):
unit = tool.Unit.get_project_currency_unit()
if unit:
return {"id": unit.id(), "name": unit.Currency}
@classmethod
def total_cost_schedules(cls):
return len(tool.Ifc.get().by_type("IfcCostSchedule"))
@@ -62,6 +69,7 @@ class CostSchedulesData:
{
"id": schedule.id(),
"name": schedule.Name or "Unnamed",
"predefined_type": ifcopenshell.util.element.get_predefined_type(schedule),
}
)
else:
@@ -70,6 +78,7 @@ class CostSchedulesData:
{
"id": schedule.id(),
"name": schedule.Name or "Unnamed",
"predefined_type": ifcopenshell.util.element.get_predefined_type(schedule),
}
)
return results
@@ -107,6 +116,7 @@ class CostSchedulesData:
data["UnitBasisUnitSymbol"] = None
data["TotalAppliedValue"] = 0.0
data["TotalCost"] = 0.0
has_unit_basis = False
if root_element.is_a("IfcCostItem"):
values = root_element.CostValues
elif root_element.is_a("IfcConstructionResource"):
@@ -119,10 +129,15 @@ class CostSchedulesData:
cost_value_data = cls._cost_values[cost_value.id()]
data["UnitBasisValueComponent"] = cost_value_data["UnitBasis"]["ValueComponent"]
data["UnitBasisUnitSymbol"] = cost_value_data["UnitBasis"]["UnitSymbol"]
if data["UnitBasisValueComponent"]:
data["TotalCost"] = data["TotalCostQuantity"] / data["UnitBasisValueComponent"] * data["TotalAppliedValue"]
has_unit_basis = True
if has_unit_basis:
data["TotalCost"] = data["TotalAppliedValue"] / data["UnitBasisValueComponent"]
else:
data["TotalCost"] = data["TotalCostQuantity"] * data["TotalAppliedValue"]
if data["TotalCostQuantity"] is not None:
data["TotalCost"] = data["TotalAppliedValue"] * data["TotalCostQuantity"]
else:
data["TotalCost"] = data["TotalAppliedValue"]
data["TotalAppliedValue"] = None
@classmethod
def _load_cost_item_quantities(cls, cost_item, data):
@@ -97,10 +97,9 @@ class AddSummaryCostItem(bpy.types.Operator, tool.Ifc.Operator):
bl_label = "Add Cost Item"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Add a summary cost item"
cost_schedule: bpy.props.IntProperty()
def _execute(self, context):
core.add_summary_cost_item(tool.Ifc, tool.Cost, cost_schedule=tool.Ifc.get().by_id(self.cost_schedule))
core.add_summary_cost_item(tool.Ifc, tool.Cost, cost_schedule=tool.Cost.get_active_cost_schedule())
class AddCostItem(bpy.types.Operator, tool.Ifc.Operator):
@@ -114,6 +113,16 @@ class AddCostItem(bpy.types.Operator, tool.Ifc.Operator):
core.add_cost_item(tool.Ifc, tool.Cost, cost_item=tool.Ifc.get().by_id(self.cost_item))
class CopyCostItem(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.copy_cost_item"
bl_label = "Copy Cost Item"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Copy a cost item"
def _execute(self, context):
core.copy_cost_item(tool.Ifc, tool.Cost)
class ExpandCostItem(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.expand_cost_item"
bl_label = "Expand Cost Item"
@@ -154,7 +163,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):
@@ -214,8 +223,9 @@ class UnassignCostItemType(bpy.types.Operator, tool.Ifc.Operator):
core.unassign_cost_item_type(
tool.Ifc,
tool.Cost,
self.cost_item,
products=[tool.Ifc.get().by_id(self.related_object)] if self.related_object else [],
tool.Spatial,
cost_item=tool.Ifc.get().by_id(self.cost_item),
product_types=[tool.Ifc.get().by_id(self.related_object)] if self.related_object else [],
)
return {"FINISHED"}
@@ -491,7 +501,7 @@ class AddCostColumn(bpy.types.Operator):
bl_options = {"REGISTER", "UNDO"}
name: bpy.props.StringProperty()
def _execute(self, context):
def execute(self, context):
core.add_cost_column(tool.Cost, self.name)
return {"FINISHED"}
@@ -619,15 +629,21 @@ class ExportCostSchedules(bpy.types.Operator):
bl_label = "Export Cost Schedule"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Export a cost schedule to a CSV, XSLX OR ODS file"
cost_schedule: bpy.props.IntProperty()
format: bpy.props.EnumProperty("Format", items=(("CSV", "CSV", ""), ("XLSX", "XLSX", ""), ("ODS", "ODS", "")))
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
def execute(self, context):
core.export_cost_schedules(tool.Cost, format=self.format)
cost_schedule = tool.Ifc.get().by_id(self.cost_schedule) if self.cost_schedule else None
r = core.export_cost_schedules(tool.Cost, filepath=self.filepath, format=self.format, cost_schedule=cost_schedule)
if isinstance(r, str):
self.report({"ERROR"}, r)
return {"FINISHED"}
def invoke(self, context, event):
wm = context.window_manager
return wm.invoke_props_dialog(self)
wm.fileselect_add(self)
return {"RUNNING_MODAL"}
def draw(self, context):
self.layout.label(text="Choose a format")
@@ -667,7 +683,9 @@ class LoadProductCostItems(bpy.types.Operator):
return True
def execute(self, context):
core.load_product_cost_items(tool.Cost, product=tool.Ifc.get().by_id(context.active_object.BIMObjectProperties.ifc_definition_id))
core.load_product_cost_items(
tool.Cost, product=tool.Ifc.get().by_id(context.active_object.BIMObjectProperties.ifc_definition_id)
)
return {"FINISHED"}
@@ -724,3 +742,12 @@ class ChangeParentCostItem(bpy.types.Operator, tool.Ifc.Operator):
if isinstance(r, str):
self.report({"WARNING"}, r)
return {"FINISHED"}
class AddCurrency(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.add_currency"
bl_label = "Add Currency"
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
core.add_currency(tool.Ifc, tool.Cost)
@@ -115,6 +115,27 @@ def get_schedule_predefined_types(self, context):
return CostSchedulesData.data["predefined_types"]
def get_currencies(self, context):
return [
("USD", "USD", "Dollar"),
("EUR", "EUR", "Euro"),
("GBP", "GBP", "Pound"),
("AUD", "AUD", "Australian Dollar"),
("CAD", "CAD", "Canadian Dollar"),
("CHF", "CHF", "Swiss Franc"),
("CNY", "CNY", "Chinese Yuan"),
("HKD", "HKD", "Hong Kong Dollar"),
("JPY", "JPY", "Japanese Yen"),
("NZD", "NZD", "New Zealand Dollar"),
("SEK", "SEK", "Swedish Krona"),
("KRW", "KRW", "South Korean Won"),
("SGD", "SGD", "Singapore Dollar"),
("NOK", "NOK", "Norwegian Krone"),
("MAD", "MAD", "Moroccan Dirham"),
("CUSTOM", "Custom currency", "Custom"),
]
class CostItem(PropertyGroup):
name: StringProperty(name="Name", update=update_cost_item_name)
identification: StringProperty(name="Identification", update=update_cost_item_identification)
@@ -128,6 +149,8 @@ class CostItemQuantity(PropertyGroup):
name: StringProperty(name="Name")
ifc_definition_id: IntProperty(name="IFC Definition ID")
total_quantity: FloatProperty(name="Total Quantity")
unit_symbol: StringProperty(name="Unit Symbol")
total_cost_quantity: FloatProperty(name="Total Quantity")
class CostItemType(PropertyGroup):
@@ -137,7 +160,7 @@ class CostItemType(PropertyGroup):
def update_cost_item_parent(self, context):
cost_item = tool.Cost.get_highlighted_cost_item()
tool.Cost.toggle_cost_item_parent(cost_item=cost_item)
tool.Cost.toggle_cost_item_parent_change(cost_item=cost_item)
def update_active_cost_item_elements(self, context):
@@ -188,6 +211,7 @@ class BIMCostProperties(PropertyGroup):
cost_value_formula: StringProperty(name="Cost Value Formula")
cost_column: StringProperty(name="Cost Column")
should_show_column_ui: BoolProperty(name="Should Show Column UI", default=False)
should_show_currency_ui: BoolProperty(name="Should Show Currency UI", default=False)
columns: CollectionProperty(name="Columns", type=StrProperty)
active_column_index: IntProperty(name="Active Column Index")
cost_item_products: CollectionProperty(name="Cost Item Products", type=CostItemQuantity)
@@ -204,7 +228,7 @@ class BIMCostProperties(PropertyGroup):
cost_item_rates: CollectionProperty(name="Cost Item Rates", type=CostItem)
active_cost_item_rate_index: IntProperty(name="Active Cost Rate Index")
contracted_cost_item_rates: StringProperty(name="Contracted Cost Item Rates", default="[]")
product_cost_items: CollectionProperty(name="Product Cost Items", type=CostItem)
product_cost_items: CollectionProperty(name="Product Cost Items", type=CostItemQuantity)
active_product_cost_item_index: IntProperty(name="Active Product Cost Item Index")
enable_reorder: BoolProperty(name="Enable Reorder", default=False)
show_nested_elements: BoolProperty(name="Show Nested Tasks", default=False, update=update_active_cost_item_elements)
@@ -213,3 +237,8 @@ class BIMCostProperties(PropertyGroup):
name="Show Nested Tasks", default=False, update=update_active_cost_item_resources
)
change_cost_item_parent: BoolProperty(name="Change Cost Item Parent", default=False, update=update_cost_item_parent)
show_cost_item_operators: BoolProperty(name="Show Cost Item Operators", default=False)
currency: EnumProperty(items=get_currencies, name="Currencies")
custom_currency: StringProperty(
name="Custom Currency", default="USD", description="Custom Currency in ISO 4217 format"
)
+122 -61
View File
@@ -21,21 +21,19 @@ import blenderbim.bim.module.cost.prop as CostProp
from bpy.types import Panel, UIList
from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.module.cost.data import CostSchedulesData
import blenderbim.tool as tool
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"
@classmethod
def poll(cls, context):
file = IfcStore.get_file()
return file and hasattr(file, "schema") and file.schema != "IFC2X3"
return tool.Blender.is_tab(context, "SCHEDULING") and tool.Ifc.get() and tool.Ifc.get().schema != "IFC2X3"
def draw(self, context):
if not CostSchedulesData.is_loaded:
@@ -43,49 +41,69 @@ class BIM_PT_cost_schedules(Panel):
self.props = context.scene.BIMCostProperties
row = self.layout.row()
if CostSchedulesData.data["total_cost_schedules"]:
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.prop(self.props, "cost_schedule_predefined_types")
row.operator("bim.add_cost_schedule", icon="ADD", text="Add new")
if not self.props.active_cost_schedule_id:
if CostSchedulesData.data["total_cost_schedules"]:
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.", icon="COMMUNITY")
row = self.layout.row()
row.alignment = "RIGHT"
row.prop(self.props, "cost_schedule_predefined_types")
row.operator("bim.add_cost_schedule", icon="ADD", text="Add")
for schedule in CostSchedulesData.data["schedules"]:
self.draw_cost_schedule_ui(schedule)
def draw_cost_schedule_ui(self, cost_schedule):
row = self.layout.row(align=True)
row.label(text=cost_schedule["name"], icon="LINENUMBERS_ON")
if self.props.active_cost_schedule_id and self.props.active_cost_schedule_id == cost_schedule["id"]:
op = row.operator("bim.select_cost_schedule_products", icon="RESTRICT_SELECT_OFF", text="Assigned")
row.label(
text="Currently editing: {}[{}]".format(cost_schedule["name"], cost_schedule["predefined_type"]),
icon="LINENUMBERS_ON",
)
grid = self.layout.grid_flow(columns=2, even_columns=True)
col = grid.column()
row1 = col.row(align=True)
row1.alignment = "LEFT"
row1.label(text="Schedule tools")
row1 = col.row(align=True)
row1.alignment = "RIGHT"
row1.operator("bim.export_cost_schedules", text="Export", icon="EXPORT").cost_schedule = cost_schedule["id"]
row2 = col.row(align=True)
row2.alignment = "RIGHT"
op = row2.operator("bim.select_cost_schedule_products", icon="RESTRICT_SELECT_OFF", text="Assigned")
op.cost_schedule = cost_schedule["id"]
row.operator("bim.select_unassigned_products", icon="RESTRICT_SELECT_OFF", text="Unassigned")
row2.operator("bim.select_unassigned_products", icon="RESTRICT_SELECT_OFF", text="Unassigned")
row.prop(self.props, "should_show_column_ui", text="", icon="SHORTDISPLAY")
col = grid.column()
row1 = col.row(align=True)
row1.alignment = "LEFT"
row1.label(text="Settings")
row1 = col.row(align=True)
row1.alignment = "RIGHT"
row1.prop(self.props, "should_show_currency_ui", text="Project Currency", icon="COPY_ID")
row1.prop(self.props, "should_show_column_ui", text="Schedule Columns", icon="SHORTDISPLAY")
if self.props.is_editing == "COST_SCHEDULE_ATTRIBUTES":
row.operator("bim.edit_cost_schedule", text="", icon="CHECKMARK")
elif self.props.is_editing == "COST_ITEMS":
row.operator("bim.add_summary_cost_item", text="", icon="ADD").cost_schedule = cost_schedule["id"]
row.operator("bim.disable_editing_cost_schedule", text="", icon="CANCEL")
elif self.props.active_cost_schedule_id:
row.operator("bim.remove_cost_schedule", text="", icon="X").cost_schedule = cost_schedule["id"]
row.operator("bim.disable_editing_cost_schedule", text="Disable Editing", icon="CANCEL")
else:
row.label(
text="{}[{}]".format(cost_schedule["name"], cost_schedule["predefined_type"]), icon="LINENUMBERS_ON"
)
row.operator("bim.enable_editing_cost_items", text="", icon="OUTLINER").cost_schedule = cost_schedule["id"]
row.operator(
"bim.enable_editing_cost_schedule_attributes", text="", icon="GREASEPENCIL"
).cost_schedule = cost_schedule["id"]
row.operator("bim.remove_cost_schedule", text="", icon="X").cost_schedule = cost_schedule["id"]
if self.props.active_cost_schedule_id == cost_schedule["id"]:
if self.props.is_editing == "COST_SCHEDULE_ATTRIBUTES":
self.draw_editable_cost_schedule_ui()
elif self.props.is_editing == "COST_ITEMS":
if self.props.should_show_column_ui:
self.draw_column_ui()
if self.props.should_show_currency_ui:
self.draw_currency_ui()
self.draw_editable_cost_item_ui()
def draw_column_ui(self):
@@ -94,6 +112,20 @@ class BIM_PT_cost_schedules(Panel):
row.operator("bim.add_cost_column", text="", icon="ADD").name = self.props.cost_column
self.layout.template_list("BIM_UL_cost_columns", "", self.props, "columns", self.props, "active_column_index")
def draw_currency_ui(self):
row = self.layout.row(align=True)
if CostSchedulesData.data["currency"]:
text = "Currency used: {}".format(CostSchedulesData.data["currency"]["name"])
row.label(text=text)
row.operator("bim.remove_unit", text="", icon="X").unit = CostSchedulesData.data["currency"]["id"]
else:
row.label(text="No currency set")
row.prop(self.props, "currency", text="")
if self.props.currency == "CUSTOM":
row.alignment = "RIGHT"
row.prop(self.props, "custom_currency", text="")
row.operator("bim.add_currency", text="", icon="ADD")
def draw_editable_cost_schedule_ui(self):
blenderbim.bim.helper.draw_attributes(self.props.cost_schedule_attributes, self.layout)
@@ -101,30 +133,39 @@ class BIM_PT_cost_schedules(Panel):
row = self.layout.row(align=True)
row.alignment = "RIGHT"
ifc_definition_id = None
row = self.layout.row(align=True)
row.label(text="Cost Item Tools")
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 = 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):
ifc_definition_id = self.props.cost_items[self.props.active_cost_item_index].ifc_definition_id
if ifc_definition_id:
row.prop(self.props, "change_cost_item_parent", text="", icon="LINKED")
row.prop(self.props, "enable_reorder", text="", icon="SORTALPHA")
if not CostSchedulesData.data["is_editing_rates"]:
op = row.operator("bim.enable_editing_cost_item_quantities", text="", icon="PROPERTIES")
row.prop(self.props, "show_cost_item_operators", text="Edit", icon="DOWNARROW_HLT")
row.operator("bim.add_cost_item", text="Add", icon="ADD").cost_item = ifc_definition_id
row.operator("bim.copy_cost_item", text="Copy", icon="ADD")
row.operator("bim.remove_cost_item", text="Delete", icon="X").cost_item = ifc_definition_id
if self.props.show_cost_item_operators:
row = self.layout.row(align=True)
row.alignment = "RIGHT"
row.prop(self.props, "change_cost_item_parent", text="", icon="LINKED")
row.prop(self.props, "enable_reorder", text="", icon="SORTALPHA")
if not CostSchedulesData.data["is_editing_rates"]:
op = row.operator("bim.enable_editing_cost_item_quantities", text="", icon="PROPERTIES")
op.cost_item = ifc_definition_id
op = row.operator("bim.enable_editing_cost_item_values", text="", icon="DISC")
op.cost_item = ifc_definition_id
op = row.operator("bim.enable_editing_cost_item_values", text="", icon="DISC")
op.cost_item = ifc_definition_id
row.operator("bim.add_cost_item", text="", icon="ADD").cost_item = ifc_definition_id
if self.props.active_cost_item_id == ifc_definition_id:
if self.props.cost_item_editing_type == "ATTRIBUTES":
row.operator("bim.edit_cost_item", text="", icon="CHECKMARK")
row.operator("bim.disable_editing_cost_item", text="", icon="CANCEL")
else:
op = row.operator("bim.enable_editing_cost_item_attributes", text="", icon="GREASEPENCIL")
op.cost_item = ifc_definition_id
row.operator("bim.remove_cost_item", text="", icon="X").cost_item = ifc_definition_id
if self.props.active_cost_item_id == ifc_definition_id:
if self.props.cost_item_editing_type == "ATTRIBUTES":
row.operator("bim.edit_cost_item", text="", icon="CHECKMARK")
row.operator("bim.disable_editing_cost_item", text="", icon="CANCEL")
else:
op = row.operator("bim.enable_editing_cost_item_attributes", text="", icon="GREASEPENCIL")
op.cost_item = ifc_definition_id
self.layout.template_list(
"BIM_UL_cost_items",
"",
@@ -225,7 +266,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"
@@ -311,7 +352,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"
@@ -483,7 +524,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"
@@ -579,7 +620,10 @@ class BIM_UL_cost_items_trait:
row.label(text="", icon="DOT")
def draw_total_cost_column(self, layout, cost_item):
layout.label(text="{0:.2f}".format(cost_item["TotalCost"]))
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"]:
@@ -588,13 +632,18 @@ class BIM_UL_cost_items_trait:
self.draw_total_quantity_column(layout, cost_item)
def draw_value_column(self, layout, cost_item):
text = "{0:.2f}".format(cost_item["TotalAppliedValue"])
if cost_item["UnitBasisValueComponent"] not in [None, 1]:
text += " / {}".format(round(cost_item["UnitBasisValueComponent"], 2))
layout.label(text=text)
if cost_item["TotalAppliedValue"]:
text = "{0:,.2f}".format(cost_item["TotalAppliedValue"]).replace(",", " ")
if cost_item["UnitBasisValueComponent"] not in [None, 1]:
text = "{} / {}".format(text, round(cost_item["UnitBasisValueComponent"], 2))
currency = CostSchedulesData.data["currency"]
text = "{} {}".format(text, currency["name"]) if currency else text
layout.label(text=text)
else:
layout.label(text="-")
def draw_uom_column(self, layout, cost_item):
layout.label(text=cost_item["UnitBasisUnitSymbol"] or "?" if cost_item["UnitBasisValueComponent"] else "-")
layout.label(text=cost_item["UnitBasisUnitSymbol"] or "-" if cost_item["UnitBasisValueComponent"] else "-")
def draw_order_operator(self, row, ifc_definition_id, cost_item):
if cost_item["NestingIndex"] is not None:
@@ -608,7 +657,18 @@ class BIM_UL_cost_items_trait:
op.new_index = cost_item["NestingIndex"] - 1
def draw_total_quantity_column(self, layout, cost_item):
layout.label(text="{0:.2f}".format(cost_item["TotalCostQuantity"]) + f" {cost_item['UnitSymbol'] or '?'}")
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):
@@ -658,14 +718,14 @@ class BIM_UL_cost_item_quantities(UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
props = context.scene.BIMCostProperties
cost_item = props.cost_items[props.active_cost_item_index]
if item:
row = layout.row(align=True)
op = row.operator("bim.select_product", text="", icon="RESTRICT_SELECT_OFF")
op.product = item.ifc_definition_id
row.split(factor=0.8)
row.label(text=item.name)
row.label(text="{0:.2f}".format(item.total_quantity))
formatted_quantity = "{0:.2f}".format(item.total_quantity)
row.label(text="{}{}".format(formatted_quantity, item.unit_symbol))
op = row.operator("bim.unassign_cost_item_quantity", text="", icon="X")
op.cost_item = cost_item.ifc_definition_id
op.related_object = item.ifc_definition_id
@@ -677,8 +737,11 @@ class BIM_UL_product_cost_items(UIList):
row = layout.row(align=True)
op = row.operator("bim.highlight_product_cost_item", text="", icon="STYLUS_PRESSURE")
op.cost_item = item.ifc_definition_id
row.split(factor=0.8)
row.split(factor=0.5)
row.label(text=item.name)
qty = "{0:.2f}".format(item.total_quantity)
formatted_total_quantity = "{0:.2f}".format(item.total_cost_quantity)
row.label(text="{}/{} {}".format(qty, formatted_total_quantity, item.unit_symbol))
class BIM_PT_Costing_Tools(Panel):
@@ -691,9 +754,7 @@ class BIM_PT_Costing_Tools(Panel):
def draw(self, context):
self.props = context.scene.BIMCostProperties
row = self.layout.row()
row.operator(
"bim.load_product_cost_items", icon="FILE_REFRESH"
)
row.operator("bim.load_product_cost_items", icon="FILE_REFRESH")
row = self.layout.row()
row.template_list(
"BIM_UL_product_cost_items",
@@ -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
@@ -31,6 +31,7 @@ from blenderbim.bim.handler import purge_module_data
class AddCsvAttribute(bpy.types.Operator):
bl_idname = "bim.add_csv_attribute"
bl_label = "Add CSV Attribute"
bl_description = "Add a new IFC Attribute to the CSV export"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
@@ -52,6 +53,7 @@ class RemoveCsvAttribute(bpy.types.Operator):
class RemoveAllCsvAttributes(bpy.types.Operator):
bl_idname = "bim.remove_all_csv_attributes"
bl_label = "Remove all CSV Attributes"
bl_description = "Remove all IFC Attributes from the CSV export"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
@@ -61,20 +63,25 @@ class RemoveAllCsvAttributes(bpy.types.Operator):
class ImportCsvAttributes(bpy.types.Operator):
bl_idname = "bim.import_csv_attributes"
bl_label = "Import CSV Attributes"
bl_label = "Import CSV Template"
bl_description = "Import a json template for CSV export"
bl_options = {"REGISTER", "UNDO"}
filter_glob: bpy.props.StringProperty(default="*.json", options={"HIDDEN"})
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
def execute(self, context):
csv_attributes = context.scene.CsvProperties.csv_attributes
csv_attributes.clear()
csv_props = context.scene.CsvProperties
csv_json = json.load(open(self.filepath))
i = 0
for attribute in csv_json:
csv_attributes.add()
csv_attributes[i].name = attribute
i += 1
expression = csv_json.get("expression", "")
if expression:
csv_props.ifc_selector = expression
attributes = csv_json.get("attributes", [])
if attributes:
csv_props.csv_attributes.clear()
for attribute in attributes:
csv_props.csv_attributes.add().name = attribute
return {"FINISHED"}
@@ -85,19 +92,30 @@ class ImportCsvAttributes(bpy.types.Operator):
class ExportCsvAttributes(bpy.types.Operator):
bl_idname = "bim.export_csv_attributes"
bl_label = "Export CSV Attributes"
bl_label = "Export CSV Template"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Save a json template for CSV export"
filename_ext = ".json"
filter_glob: bpy.props.StringProperty(default="*.json", options={"HIDDEN"})
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
def execute(self, context):
csv_attributes = []
with open(self.filepath, "w") as outfile:
for attribute in context.scene.CsvProperties.csv_attributes:
csv_attributes.append(attribute.name)
csv_props = context.scene.CsvProperties
json.dump(csv_attributes, outfile)
csv_template = {}
expression = csv_props.ifc_selector
if expression:
csv_template["expression"] = expression
csv_attributes = []
for attribute in csv_props.csv_attributes:
attribute_name = attribute.name
csv_attributes.append(attribute_name)
if csv_attributes:
csv_template["attributes"] = csv_attributes
with open(self.filepath, "w") as outfile:
json.dump(csv_template, outfile)
return {"FINISHED"}
@@ -110,12 +128,14 @@ class ExportCsvAttributes(bpy.types.Operator):
class ExportIfcCsv(bpy.types.Operator):
bl_idname = "bim.export_ifccsv"
bl_label = "Export IFC to CSV"
filename_ext = ".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"}
@@ -124,7 +144,7 @@ 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:
@@ -132,14 +152,9 @@ class ExportIfcCsv(bpy.types.Operator):
selector = ifcopenshell.util.selector.Selector()
results = selector.parse(ifc_file, props.ifc_selector)
ifc_csv = ifccsv.IfcCsv()
ifc_csv.output = self.filepath
ifc_csv.attributes = [a.name for a in props.csv_attributes]
ifc_csv.selector = selector
if props.csv_delimiter == "CUSTOM":
ifc_csv.delimiter = props.csv_custom_delimiter
else:
ifc_csv.delimiter = props.csv_delimiter
ifc_csv.export(ifc_file, results)
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=props.format, delimiter=sep)
return {"FINISHED"}
@@ -164,12 +179,8 @@ class ImportIfcCsv(bpy.types.Operator):
else:
ifc_file = ifcopenshell.open(props.csv_ifc_file)
ifc_csv = ifccsv.IfcCsv()
ifc_csv.output = self.filepath
if props.csv_delimiter == "CUSTOM":
ifc_csv.delimiter = props.csv_custom_delimiter
else:
ifc_csv.delimiter = props.csv_delimiter
ifc_csv.Import(ifc_file)
sep = props.csv_custom_delimiter if props.csv_delimiter == "CUSTOM" else props.csv_delimiter
ifc_csv.Import(ifc_file, self.filepath, delimiter=sep)
if not props.should_load_from_memory:
ifc_file.write(props.csv_ifc_file)
purge_module_data()
@@ -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")
+18 -23
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
@@ -49,37 +49,32 @@ class BIM_PT_ifccsv(Panel):
row.operator("bim.eyedrop_ifccsv", icon="EYEDROPPER", text="")
layout.separator()
row = layout.row()
split = row.split(factor=0.7)
c = split.column()
c.operator("bim.add_csv_attribute")
c = split.column()
c.operator("bim.import_csv_attributes", icon="IMPORT", text="Load Template")
row = layout.row(align=True)
row.operator("bim.add_csv_attribute", icon="ADD")
row.operator("bim.remove_all_csv_attributes", icon="CANCEL")
row = layout.row(align=True)
row.operator("bim.import_csv_attributes", icon="IMPORT")
row.operator("bim.export_csv_attributes", icon="EXPORT")
for index, attribute in enumerate(props.csv_attributes):
row = layout.row(align=True)
row.prop(attribute, "name", text="")
row.operator("bim.remove_csv_attribute", icon="X", text="").index = index
if props.csv_attributes:
row = layout.row()
row.label()
row.operator("bim.remove_all_csv_attributes", icon="CANCEL", text="")
row = layout.row()
row.operator("bim.export_csv_attributes", icon="EXPORT", text="Create Template")
layout.separator()
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.7)
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,10 +24,13 @@ 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.AddSheet,
@@ -35,10 +38,13 @@ classes = (
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,
@@ -47,30 +53,37 @@ 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.RemoveSheet,
operator.RemoveTextLiteral,
operator.SelectAllDrawings,
operator.ResizeText,
operator.SaveDrawingStyle,
operator.SaveDrawingStylesData,
operator.SelectAllDrawings,
operator.SelectAssignedProduct,
operator.SelectDocIfcFile,
prop.Variable,
prop.Drawing,
prop.Schedule,
prop.Document,
prop.DrawingStyle,
prop.Sheet,
prop.DocProperties,
@@ -81,10 +94,10 @@ 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,
ui.BIM_PT_references,
ui.BIM_PT_product_assignments,
ui.BIM_PT_text,
ui.BIM_UL_drawinglist,
@@ -94,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=True, 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)
@@ -20,6 +20,7 @@ import bpy
import os
import blenderbim.tool as tool
from mathutils import Vector
import bmesh
class Annotator:
@@ -44,7 +45,7 @@ class Annotator:
font.name = "OpenGost Type B TT"
obj.data.font = font
obj.data.BIMTextProperties.font_size = "2.5"
collection = bpy.context.scene.camera.users_collection[0]
collection = bpy.context.scene.camera.BIMObjectProperties.collection
collection.objects.link(obj)
Annotator.resize_text(obj)
return obj
@@ -52,9 +53,10 @@ class Annotator:
@staticmethod
def resize_text(text_obj):
camera = None
for obj in text_obj.users_collection[0].objects:
if isinstance(obj.data, bpy.types.Camera):
camera = obj
group = tool.Drawing.get_drawing_group(tool.Ifc.get_entity(text_obj))
for element in tool.Drawing.get_drawing_elements(group):
if element.is_a("IfcAnnotation") and element.ObjectType == "DRAWING":
camera = tool.Ifc.get_object(element)
break
if not camera:
return
@@ -62,12 +64,7 @@ class Annotator:
font_size = 1.6 / 1000
font_size *= float(text_obj.data.BIMTextProperties.font_size)
if camera.data.BIMCameraProperties.diagram_scale == "CUSTOM":
human_scale, fraction = camera.data.BIMCameraProperties.custom_diagram_scale.split("|")
else:
human_scale, fraction = camera.data.BIMCameraProperties.diagram_scale.split("|")
numerator, denominator = fraction.split("/")
font_size /= float(numerator) / float(denominator)
font_size /= tool.Drawing.get_scale_ratio(tool.Drawing.get_diagram_scale(camera)["Scale"])
text_obj.data.size = font_size
@@ -94,7 +91,7 @@ class Annotator:
return obj
@staticmethod
def add_plane_to_annotation(obj):
def add_plane_to_annotation(obj, remove_face=False):
# default order = bot left, top left, bot right, top right
# therefore we redefine the order
face_verts = [0, 2, 3, 1]
@@ -104,7 +101,9 @@ class Annotator:
bm = tool.Blender.get_bmesh_for_mesh(obj.data, clean=True)
new_verts = [bm.verts.new(v) for v in verts_local]
bm.faces.new([new_verts[i] for i in face_verts])
face = bm.faces.new([new_verts[i] for i in face_verts])
if remove_face:
bmesh.ops.delete(bm, geom=[face], context="FACES_ONLY")
tool.Blender.apply_bmesh(obj.data, bm, obj)
return obj
@@ -114,7 +113,7 @@ class Annotator:
co1, _, _, _ = Annotator.get_placeholder_coords(camera)
matrix_world = camera.matrix_world.copy()
matrix_world.translation = co1
collection = camera.users_collection[0]
collection = camera.BIMObjectProperties.collection
if object_type == "TEXT":
obj = bpy.data.objects.new(object_type, None)
@@ -153,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
@@ -163,9 +163,13 @@ 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
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,
)
@@ -27,11 +27,12 @@ from pathlib import Path
def refresh():
ProductAssignmentsData.is_loaded = False
SheetsData.is_loaded = False
SchedulesData.is_loaded = False
DocumentsData.is_loaded = False
DrawingsData.is_loaded = False
AnnotationData.is_loaded = False
DecoratorData.data = {}
DecoratorData.cut_cache = {}
DecoratorData.layerset_cache = {}
class ProductAssignmentsData:
@@ -102,6 +103,7 @@ class DrawingsData:
"has_saved_ifc": cls.has_saved_ifc(),
"total_drawings": cls.total_drawings(),
"location_hint": cls.location_hint(),
"active_drawing_pset_data": cls.active_drawing_pset_data(),
}
cls.is_loaded = True
@@ -123,14 +125,28 @@ class DrawingsData:
return results
return [(h.upper(), h, "") for h in ["North", "South", "East", "West"]]
@classmethod
def active_drawing_pset_data(cls):
ifc_file = tool.Ifc.get()
drawing_id = bpy.context.scene.DocProperties.active_drawing_id
if drawing_id == 0:
return {}
drawing = ifc_file.by_id(bpy.context.scene.DocProperties.active_drawing_id)
return ifcopenshell.util.element.get_pset(drawing, "EPset_Drawing")
class SchedulesData:
class DocumentsData:
data = {}
is_loaded = False
@classmethod
def load(cls):
cls.data = {"has_saved_ifc": cls.has_saved_ifc(), "total_schedules": cls.total_schedules()}
documents = cls.count_documents()
cls.data = {
"has_saved_ifc": cls.has_saved_ifc(),
"total_schedules": documents["SCHEDULE"],
"total_references": documents["REFERENCE"],
}
cls.is_loaded = True
@classmethod
@@ -138,8 +154,16 @@ class SchedulesData:
return os.path.isfile(tool.Ifc.get_path())
@classmethod
def total_schedules(cls):
return len([d for d in tool.Ifc.get().by_type("IfcDocumentInformation") if d.Scope == "SCHEDULE"])
def count_documents(cls):
documents = {
"SCHEDULE": 0,
"REFERENCE": 0,
}
for d in tool.Ifc.get().by_type("IfcDocumentInformation"):
scope = d.Scope
documents[scope] = documents.get(scope, 0) + 1
return documents
FONT_SIZES = {
@@ -155,6 +179,7 @@ class DecoratorData:
# stores 1 type of data per object
data = {}
cut_cache = {}
layerset_cache = {}
# used by Ifc Annotations with ObjectType = "BATTING"
@classmethod
@@ -226,18 +251,21 @@ class DecoratorData:
props = obj.BIMTextProperties
# getting font size
classes = ifcopenshell.util.element.get_pset(element, "EPset_Annotation", "Classes")
pset_data = ifcopenshell.util.element.get_pset(element, "EPset_Annotation") or {}
# use `regular` as default
if classes:
classes_split = classes.split()
# prioritize smaller font sizes just like in svg
font_size_type = next(
(font_size_type for font_size_type in FONT_SIZES if font_size_type in classes_split), "regular"
)
else:
font_size_type = "regular"
# get font size
classes = pset_data.get("Classes", None) or "regular"
classes_split = classes.split()
# prioritize smaller font sizes just like in svg
font_size_type = next(
(font_size_type for font_size_type in FONT_SIZES if font_size_type in classes_split), "regular"
)
font_size = FONT_SIZES[font_size_type]
# get symbol
symbol = pset_data.get("Symbol", None)
# other attributes
props_literals = props.literals
props_literals_n = len(props.literals)
@@ -255,35 +283,48 @@ class DecoratorData:
literals_data.append(literal_data)
text_data = {"Literals": literals_data, "FontSize": font_size}
text_data = {"Literals": literals_data, "FontSize": font_size, "Symbol": symbol}
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", None) or ""
text_suffix = pset_data.get("TextSuffix", None) or ""
dimension_data = {
"dimension_style": dimension_style,
"show_description_only": show_description_only,
"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
@@ -297,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"])
File diff suppressed because it is too large Load Diff
@@ -23,7 +23,7 @@ from bpy import types
from mathutils import Vector
from mathutils import geometry
from bpy_extras import view3d_utils
from blenderbim.bim.module.drawing.shaders import DotsGizmoShader, ExtrusionGuidesShader, BaseLinesShader
from blenderbim.bim.module.drawing.shaders import DotsGizmoShader, ExtrusionGuidesShader
from ifcopenshell.util.unit import si_conversions
@@ -251,11 +251,12 @@ X3DISC = (
class CustomGizmo:
# FIXME: highliting/selection doesnt work
def draw_very_custom_shape(self, ctx, custom_shape, select_id=None):
# similar to draw_custom_shape
shape, batch, shader = custom_shape
# create shader and batch
shader_wrapper, batch = custom_shape
shader = shader_wrapper.get_shader()
# setup params
shader.bind()
if select_id is not None:
gpu.select.load_id(select_id)
else:
@@ -264,13 +265,17 @@ class CustomGizmo:
else:
color = (*self.color, self.alpha)
shader.uniform_float("color", color)
shape.glenable()
shader_wrapper.glenable()
shader_wrapper.uniform_region(ctx)
shape.uniform_region(ctx)
# shader.uniform_float('modelMatrix', self.matrix_world)
# using `with` block to make sure matrix multiplication
# won't affect other shaders
with gpu.matrix.push_pop():
gpu.matrix.multiply_matrix(self.matrix_world)
batch.draw()
# using matrix_world seems to be unaffected by matrix_offset
# therefore we use basis @ offset
matrix = self.matrix_basis @ self.matrix_offset
gpu.matrix.multiply_matrix(matrix)
batch.draw(shader)
gpu.state.blend_set("NONE")
@@ -349,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()
@@ -360,6 +365,7 @@ class UglyDotGizmo(OffsetHandle, types.Gizmo):
self.draw_custom_shape(self.custom_shape, select_id=select_id)
# TODO: dead code?
class DotGizmo(CustomGizmo, OffsetHandle, types.Gizmo):
"""Single dot viewport-aligned"""
@@ -379,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()
@@ -389,10 +395,6 @@ class DotGizmo(CustomGizmo, OffsetHandle, types.Gizmo):
self.refresh()
self.draw_very_custom_shape(ctx, self.custom_shape, select_id=select_id)
# doesn't get called
# def test_select(self, ctx, location):
# pass
class ExtrusionGuidesGizmo(CustomGizmo, types.Gizmo):
"""Extrusion guides
@@ -407,19 +409,25 @@ class ExtrusionGuidesGizmo(CustomGizmo, types.Gizmo):
__slots__ = ("scale_value", "custom_shape")
def setup(self):
shader = ExtrusionGuidesShader()
self.custom_shape = shader, shader.batch(pos=((0, 0, 0), (0, 0, 1))), shader.prog
self.use_draw_scale = False
def refresh(self):
depth = self.target_get_value("depth") / self.scale_value
self.matrix_offset.col[2][2] = depth # z-scaled
"""setup `custom_shape`"""
shader_wrapper = ExtrusionGuidesShader()
verts = [Vector((0, 0, 0)), Vector((0, 0, 1))]
verts, edges = shader_wrapper.process_geometry(verts)
self.custom_shape = shader_wrapper, shader_wrapper.batch(
pos=verts,
indices=edges,
)
def draw(self, ctx):
self.refresh()
self.draw_very_custom_shape(ctx, self.custom_shape)
def refresh(self):
depth = self.target_get_value("depth") / self.scale_value
self.matrix_offset.col[2][2] = depth # z-scaled
# TODO: dead code?
class DimensionLabelGizmo(types.Gizmo):
"""Text label for a dimension"""
@@ -435,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)
@@ -486,6 +494,7 @@ class ExtrusionWidget(types.GizmoGroup):
theme = ctx.preferences.themes[0].user_interface
scale_value = self.get_scale_value(ctx.scene.unit_settings.system, ctx.scene.unit_settings.length_unit)
# setup handle
gz = self.handle = self.gizmos.new("BIM_GT_uglydot_3d")
gz.matrix_basis = basis
gz.scale_basis = 0.1
@@ -496,6 +505,7 @@ class ExtrusionWidget(types.GizmoGroup):
gz.target_set_prop("offset", prop, "value")
gz.scale_value = scale_value
# setup guides
gz = self.guides = self.gizmos.new("BIM_GT_extrusion_guides")
gz.matrix_basis = basis
gz.color = gz.color_highlight = tuple(theme.gizmo_secondary)
@@ -154,13 +154,14 @@ def format_distance(
# Separate ft and inches
# Unless Inches are the specified Length Unit
if unit_length != "INCHES":
feet = math.floor(decInches / inPerFoot)
feet = int(decInches / inPerFoot) # remove decimal
decInches -= feet * inPerFoot
else:
feet = 0
# Separate Fractional Inches
inches = math.floor(decInches)
decInches = abs(decInches) # ignore the sign for inches
inches = math.floor(decInches) # remove decimal
if inches != 0:
frac = round(base * (decInches - inches))
else:
@@ -191,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:
@@ -207,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"
@@ -227,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"
@@ -236,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"
@@ -268,7 +271,7 @@ def get_active_drawing(scene):
props = scene.DocProperties
try:
camera = tool.Ifc.get_object(tool.Ifc.get().by_id(props.active_drawing_id))
return camera.users_collection[0], camera
return camera.BIMObjectProperties.collection, camera
except:
return None, None
File diff suppressed because it is too large Load Diff
@@ -65,12 +65,27 @@ def get_location_hint(self, context):
def update_diagram_scale(self, context):
scale = self.diagram_scale
if scale == "CUSTOM":
scale = self.custom_diagram_scale
if "|" not in scale:
try:
element = (
tool.Ifc.get()
.by_id(self.id_data.BIMMeshProperties.ifc_definition_id)
.OfProductRepresentation[0]
.ShapeOfProduct[0]
)
except:
return
human_scale, scale = scale.split("|")
diagram_scale = tool.Drawing.get_diagram_scale(tool.Ifc.get_object(element))
if not diagram_scale:
return
pset = ifcopenshell.util.element.get_pset(element, "EPset_Drawing")
if pset:
pset = tool.Ifc.get().by_id(pset["id"])
else:
pset = ifcopenshell.api.run("pset.add_pset", tool.Ifc.get(), product=element, name="EPset_Drawing")
ifcopenshell.api.run("pset.edit_pset", tool.Ifc.get(), pset=pset, properties=diagram_scale)
def update_is_nts(self, context):
try:
element = (
tool.Ifc.get()
@@ -85,9 +100,7 @@ def update_diagram_scale(self, context):
pset = tool.Ifc.get().by_id(pset["id"])
else:
pset = ifcopenshell.api.run("pset.add_pset", tool.Ifc.get(), product=element, name="EPset_Drawing")
ifcopenshell.api.run(
"pset.edit_pset", tool.Ifc.get(), pset=pset, properties={"Scale": scale, "HumanScale": human_scale}
)
ifcopenshell.api.run("pset.edit_pset", tool.Ifc.get(), pset=pset, properties={"IsNTS": self.is_nts})
def get_diagram_scales(self, context):
@@ -152,17 +165,37 @@ 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 update_schedule_name(self, context):
schedule = tool.Ifc.get().by_id(self.ifc_definition_id)
core.update_schedule_name(tool.Ifc, tool.Drawing, schedule=schedule, name=self.name)
def get_drawing_style_name(self):
"""needed to make `set_drawing_style_name` work"""
return self.get("name", "")
def set_drawing_style_name(self, new_value):
"""ensure the name is unique"""
scene = bpy.context.scene
drawing_styles = [s.name for s in scene.DocProperties.drawing_styles if s.name != self.name]
new_value = tool.Blender.ensure_unique_name(new_value, drawing_styles)
old_value = self.name
self["name"] = new_value
bpy.ops.bim.save_drawing_styles_data(rename_style=True, rename_style_from=old_value, rename_style_to=new_value)
def update_document_name(self, context):
document = tool.Ifc.get().by_id(self.ifc_definition_id)
core.update_document_name(tool.Ifc, tool.Drawing, document=document, name=self.name)
def update_has_underlay(self, context):
update_layer(self, context, "HasUnderlay", self.has_underlay)
# making sure that camera is active
if self.has_underlay and (context.active_object and context.active_object.data == self.id_data):
bpy.ops.bim.reload_drawing_styles()
bpy.ops.bim.activate_drawing_style()
def update_has_linework(self, context):
@@ -199,8 +232,11 @@ def toggleDecorations(self, context):
toggle = self.should_draw_decorations
if toggle:
# TODO: design a proper text variable templating renderer
collection = context.scene.camera.users_collection[0]
collection = context.scene.camera.BIMObjectProperties.collection
for obj in collection.objects:
element = tool.Ifc.get_entity(obj)
if not element or not tool.Drawing.is_annotation_object_type(element, ["TEXT", "TEXT_LEADER"]):
continue
tool.Drawing.update_text_value(obj)
refresh_drawing_data()
decoration.DecorationsHandler.install(context)
@@ -218,11 +254,13 @@ 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 Schedule(PropertyGroup):
class Document(PropertyGroup):
ifc_definition_id: IntProperty(name="IFC Definition ID")
name: StringProperty(name="Name", update=update_schedule_name)
name: StringProperty(name="Name", update=update_document_name)
identification: StringProperty(name="Identification")
@@ -236,8 +274,8 @@ class Sheet(PropertyGroup):
class DrawingStyle(PropertyGroup):
name: StringProperty(name="Name")
raster_style: StringProperty(name="Raster Style")
name: StringProperty(name="Name", get=get_drawing_style_name, set=set_drawing_style_name)
raster_style: StringProperty(name="Raster Style", default="{}")
render_type: EnumProperty(
items=[
("NONE", "None", ""),
@@ -253,30 +291,21 @@ class DrawingStyle(PropertyGroup):
class RasterStyleProperty(enum.Enum):
WORLD_COLOR = "bpy.data.worlds[0].color"
RENDER_ENGINE = "scene.render.engine"
RENDER_TRANSPARENT = "scene.render.film_transparent"
VIEW_TRANSFORM = "scene.view_settings.view_transform"
SHADING_SHOW_OBJECT_OUTLINE = "scene.display.shading.show_object_outline"
SHADING_SHOW_CAVITY = "scene.display.shading.show_cavity"
SHADING_CAVITY_TYPE = "scene.display.shading.cavity_type"
SHADING_CURVATURE_RIDGE_FACTOR = "scene.display.shading.curvature_ridge_factor"
SHADING_CURVATURE_VALLEY_FACTOR = "scene.display.shading.curvature_valley_factor"
SHADING_LIGHT = "scene.display.shading.light"
SHADING_COLOR_TYPE = "scene.display.shading.color_type"
SHADING_SINGLE_COLOR = "scene.display.shading.single_color"
SHADING_SHOW_SHADOWS = "scene.display.shading.show_shadows"
SHADING_SHADOW_INTENSITY = "scene.display.shading.shadow_intensity"
DISPLAY_LIGHT_DIRECTION = "scene.display.light_direction"
VIEW_USE_CURVE_MAPPING = "scene.view_settings.use_curve_mapping"
OVERLAY_SHOW_WIREFRAMES = "space.overlay.show_wireframes"
OVERLAY_WIREFRAME_THRESHOLD = "space.overlay.wireframe_threshold"
OVERLAY_SHOW_FLOOR = "space.overlay.show_floor"
OVERLAY_SHOW_AXIS_X = "space.overlay.show_axis_x"
OVERLAY_SHOW_AXIS_Y = "space.overlay.show_axis_y"
OVERLAY_SHOW_AXIS_Z = "space.overlay.show_axis_z"
OVERLAY_SHOW_OBJECT_ORIGINS = "space.overlay.show_object_origins"
OVERLAY_SHOW_RELATIONSHIP_LINES = "space.overlay.show_relationship_lines"
# EVAL_PROP_ props will be evaluated explicitly
EVAL_PROP_WORLD_COLOR = "bpy.data.worlds[0].color"
# those props attributes used as a source for shading style properties
RENDER = "scene.render"
VIEW_SETTINGS = "scene.view_settings"
SHADING = "scene.display.shading"
DISPLAY = "scene.display"
OVERLAY = "space.overlay"
SPACE_SHADING = "space.shading"
RASTER_STYLE_PROPERTIES_EXCLUDE = (
"scene.render.filepath",
)
class DocProperties(PropertyGroup):
@@ -286,6 +315,7 @@ class DocProperties(PropertyGroup):
should_extract: BoolProperty(name="Should Extract", default=True)
is_editing_drawings: BoolProperty(name="Is Editing Drawings", default=False)
is_editing_schedules: BoolProperty(name="Is Editing Schedules", default=False)
is_editing_references: BoolProperty(name="Is Editing References", default=False)
target_view: EnumProperty(
items=[
("PLAN_VIEW", "Plan", ""),
@@ -303,8 +333,10 @@ class DocProperties(PropertyGroup):
active_drawing_id: IntProperty(name="Active Drawing Id")
active_drawing_index: IntProperty(name="Active Drawing Index")
current_drawing_index: IntProperty(name="Current Drawing Index")
schedules: CollectionProperty(name="Schedules", type=Schedule)
schedules: CollectionProperty(name="Schedules", type=Document)
active_schedule_index: IntProperty(name="Active Schedule Index")
references: CollectionProperty(name="References", type=Document)
active_reference_index: IntProperty(name="Active Reference Index")
titleblock: EnumProperty(items=get_titleblocks, name="Titleblock", update=update_titleblocks)
is_editing_sheets: BoolProperty(name="Is Editing Sheets", default=False)
sheets: CollectionProperty(name="Sheets", type=Sheet)
@@ -312,9 +344,6 @@ class DocProperties(PropertyGroup):
ifc_files: CollectionProperty(name="IFCs", type=StrProperty)
drawing_styles: CollectionProperty(name="Drawing Styles", type=DrawingStyle)
should_draw_decorations: BoolProperty(name="Should Draw Decorations", update=toggleDecorations)
decorations_colour: FloatVectorProperty(
name="Decorations Colour", subtype="COLOR", default=(1, 1, 1, 1), min=0.0, max=1.0, size=4
)
sheets_dir: StringProperty(default=os.path.join("sheets") + os.path.sep, name="Default Sheets Directory")
layouts_dir: StringProperty(default=os.path.join("layouts") + os.path.sep, name="Default Layouts Directory")
titleblocks_dir: StringProperty(
@@ -327,6 +356,10 @@ class DocProperties(PropertyGroup):
markers_path: StringProperty(default=os.path.join("drawings", "assets", "markers.svg"), name="Default Markers")
symbols_path: StringProperty(default=os.path.join("drawings", "assets", "symbols.svg"), name="Default Symbols")
patterns_path: StringProperty(default=os.path.join("drawings", "assets", "patterns.svg"), name="Default Patterns")
shadingstyles_path: StringProperty(
default=os.path.join("drawings", "assets", "shading_styles.json"), name="Default Shading Styles"
)
shadingstyle_default: StringProperty(default="Blender Default", name="Default Shading Style")
class BIMCameraProperties(PropertyGroup):
@@ -338,10 +371,11 @@ class BIMCameraProperties(PropertyGroup):
representation: StringProperty(name="Representation")
view_name: StringProperty(name="View Name")
diagram_scale: EnumProperty(items=get_diagram_scales, name="Drawing Scale", update=update_diagram_scale)
custom_diagram_scale: StringProperty(name="Custom Scale", update=update_diagram_scale)
custom_scale_numerator: bpy.props.StringProperty(default="1", update=update_diagram_scale)
custom_scale_denominator: bpy.props.StringProperty(default="100", update=update_diagram_scale)
raster_x: IntProperty(name="Raster X", default=1000)
raster_y: IntProperty(name="Raster Y", default=1000)
is_nts: BoolProperty(name="Is NTS")
is_nts: BoolProperty(name="Is NTS", update=update_is_nts)
active_drawing_style_index: IntProperty(name="Active Drawing Style Index")
# For now, this JSON dump are all the parameters that determine a camera's "Block representation"
@@ -465,12 +499,12 @@ 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"),
"FALL": ("Fall", "", "SORT_ASC", "curve"),
}
@@ -479,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):
@@ -494,16 +525,20 @@ def update_annotation_object_type(self, context):
AnnotationData.is_loaded = False
def update_sheet_data(self, context):
SheetsData.is_loaded = False
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")
@@ -16,12 +16,41 @@
# You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
from blenderbim.bim.module.drawing.svgwriter import SvgWriter
import svgwrite
from odf.opendocument import load
from odf.opendocument import load as load_ods
from odf.table import Table, TableRow, TableColumn, TableCell
from odf.text import P
from odf.style import Style
from textwrap import wrap
from pathlib import Path
import string
FONT_SIZE = 4.13
FONT_WIDTH = lambda size: size * 0.45
FONT_SIZE_PT = 12
FONT_FAMILY = "OpenGost Type B TT"
DEBUG = False
def col2num(col):
"""convert letter column index to number:
`"A" -> 1`, `"AA" -> 27``
"""
num = 0
for c in col:
if c in string.ascii_letters:
num = num * 26 + (ord(c.upper()) - ord("A")) + 1
return num
def a1_to_rc(cell):
"""convert cell index from A1 format to RC: `"A1" -> (0,0)`"""
column_letter = cell.strip(string.digits)
col_number = col2num(column_letter) - 1
row_number = int(cell[len(column_letter) :]) - 1
return row_number, col_number
class Scheduler:
@@ -33,69 +62,334 @@ class Scheduler:
)
self.padding = 1
self.margin = 1
doc = load(infile)
doc = load_ods(infile)
# useful for debugging ods
if DEBUG:
import xml.dom.minidom
path = Path(infile)
dom = xml.dom.minidom.parseString(doc.xml())
pretty_xml = dom.toprettyxml()
with open(path.with_suffix(".xml"), "w") as fo:
fo.write(pretty_xml)
styles = {}
for style in doc.getElementsByType(Style):
name = style.getAttribute("name")
if not style.firstChild:
for cell_style in doc.getElementsByType(Style):
name = cell_style.getAttribute("name")
styles[name] = {}
# NOTE: there are also styles that inherit from parent styles that we do not process atm
if not cell_style.firstChild:
continue
styles[name] = {key[1]: value for key, value in style.firstChild.attributes.items()}
if cell_style.firstChild.tagName in ["style:table-column-properties", "style:table-row-properties"]:
style_children = [cell_style.firstChild]
else:
# for style:table-cell-properties we need to collect also text and paragraph properties
style_children = cell_style.childNodes
for child in style_children:
child_params = {key[1]: value for key, value in child.attributes.items()}
styles[name].update(child_params)
table = doc.getElementsByType(Table)[0]
# related styles stored as a list of tuples:
# [(child, parent), ...]
related_styles = []
# collect columns width
column_widths = []
column_styles = []
for col in table.getElementsByType(TableColumn):
style_name = col.getAttribute("stylename")
repeat = col.getAttribute("numbercolumnsrepeated")
repeat = int(repeat) if repeat else 1
for i in range(0, repeat):
col_repeat = col.getAttribute("numbercolumnsrepeated")
col_repeat = int(col_repeat) if col_repeat else 1
for i in range(col_repeat):
if not style_name or "column-width" not in styles[style_name]:
column_widths.append(50)
column_width = 50
else:
column_widths.append(self.convert_to_mm(styles[style_name]["column-width"]))
column_width = self.convert_to_mm(styles[style_name]["column-width"])
column_styles.append(style_name)
column_widths.append(column_width)
cell_style = col.getAttribute("defaultcellstylename")
if cell_style:
related_styles.append((style_name, cell_style))
# collect rows height
row_heights = []
# TODO: never used yet because unsure about priority for row styles
# over column styles or vice versa
row_styles = []
for col in table.getElementsByType(TableRow):
style_name = col.getAttribute("stylename")
row_repeat = col.getAttribute("numberrowsrepeated")
row_repeat = int(row_repeat) if row_repeat else 1
for i in range(row_repeat):
if not style_name or "row-height" not in styles[style_name]:
row_height = 6
else:
row_height = self.convert_to_mm(styles[style_name]["row-height"])
row_styles.append(style_name)
row_heights.append(row_height)
cell_style = col.getAttribute("defaultcellstylename")
if cell_style:
related_styles.append((style_name, cell_style))
while len(related_styles) > 0:
# unzip related styles to children and parents
children, parents = zip(*related_styles)
independent_styles = set(parents) - set(children)
for relation in related_styles[:]:
child, parent = relation
if parent in independent_styles:
child_style = styles[child]
styles[child] = styles[parent] | child_style
related_styles.remove(relation)
# TODO: multiple print ranges? 😔
print_range = table.getAttribute("printranges")
if print_range:
min_rc, max_rc = [a1_to_rc(cell.rsplit(".", 1)[1]) for cell in print_range.split(":")]
else:
# fallback if print range is not defined
n_rows = len(row_heights)
n_cols = len(column_widths)
n_cells = len(row_heights) * len(column_widths)
cells_limit = 10000
if n_cells >= cells_limit:
raise Exception(
f"You were about to build a very big table with number of cells more than {cells_limit}.\n"
f"In fact it is {n_rows} rows x {n_cols} cols = {n_cells} cells \n"
"and the operation was stopped to prevent system freeze.\n"
"Please define print range in .ods file to proceede\n"
"(needed to make sure printed table will have reasonable size)."
)
min_rc, max_rc = (0, 0), (1048576, 16384)
min_row, min_col = min_rc
max_row, max_col = max_rc
# draw table
y = self.margin
for tri, tr in enumerate(table.getElementsByType(TableRow)):
x = self.margin
height = 6
tdi = 0
for td in tr.getElementsByType(TableCell):
repeat = td.getAttribute("numbercolumnsrepeated")
repeat = int(repeat) if repeat else 1
for i in range(0, repeat):
width = column_widths[tdi]
self.svg.add(
self.svg.rect(
insert=(x, y),
size=(width, height),
style="fill: #ffffff; stroke-width:.125; stroke: #000000;",
tri = 0
stop_iterating_over_rows = False
# TODO: row spans support?
for tr in table.getElementsByType(TableRow):
if stop_iterating_over_rows:
break
row_repeat = tr.getAttribute("numberrowsrepeated")
row_repeat = int(row_repeat) if row_repeat else 1
for i_row_repeat in range(row_repeat):
if tri < min_row:
tri += 1
continue
elif tri > max_row:
stop_iterating_over_rows = True
break
x = self.margin
height = row_heights[tri]
tdi = 0
stop_iterating_over_columns = False
for td in tr.getElementsByType(TableCell):
if stop_iterating_over_columns:
break
column_span = td.getAttribute("numbercolumnsspanned")
column_span = int(column_span) if column_span else 1
col_repeat = td.getAttribute("numbercolumnsrepeated")
col_repeat = int(col_repeat) if col_repeat else 1
# figuring text alignment
cell_style = self.get_style(td.getAttribute("stylename"), styles)
# drawing cells and text
for i_col_repeat in range(col_repeat):
start_tdi = tdi
end_tdi = tdi + int(column_span) - 1
# if the entire span is beyond print range => continue
# if only part then keeping that part
if start_tdi < min_col:
if end_tdi < min_col:
tdi += column_span
continue
else:
start_tdi = min_col
# stop if start column is beyond print range
if start_tdi > max_col:
stop_iterating_over_columns = True
break
# making sure last column won't go beyond the print range
if end_tdi > max_col:
end_tdi = max_col
width = sum(column_widths[start_tdi : end_tdi + 1])
col_style = self.get_style(column_styles[tdi], styles)
final_cell_style = cell_style or col_style
background_color = final_cell_style.get("background-color", "#ffffff")
self.svg.add(
self.svg.rect(
insert=(x, y),
size=(width, height),
style=f"fill: {background_color}; stroke-width:.125; stroke: #000000;",
)
)
)
value = td.getElementsByType(P)
if value:
self.add_text(value[0], x + self.padding, y + self.padding)
x += width
tdi += 1
y += height
total_width = sum(column_widths) + (self.margin * 2)
p_tags = td.getElementsByType(P)
text_color = final_cell_style.get("color", None)
box_alignment = self.get_box_alignment(final_cell_style)
wrap_text = final_cell_style.get("wrap-option", None) == "wrap"
bold_text = final_cell_style.get("font-weight", None) == "bold"
italic_text = final_cell_style.get("font-style", None) == "italic"
# NOTE: very naive since we're scaling text proportionally
font_size = (
float(final_cell_style.get("font-size", f"{FONT_SIZE_PT}pt")[:-2])
/ FONT_SIZE_PT
* FONT_SIZE
)
if p_tags:
# figuring text position based on alignment
text_position = [0.0, 0.0]
if box_alignment.endswith("left"):
text_position[0] = x + self.padding
elif box_alignment.endswith("middle") or box_alignment == "center":
text_position[0] = x + width / 2
elif box_alignment.endswith("right"):
text_position[0] = x + width - self.padding
if box_alignment.startswith("top"):
text_position[1] = y + self.padding
elif box_alignment.startswith("middle") or box_alignment == "center":
text_position[1] = y + height / 2
elif box_alignment.startswith("bottom"):
text_position[1] = y + height - self.padding
self.add_text(
p_tags,
*text_position,
font_size=font_size,
box_alignment=box_alignment,
wrap_text=wrap_text,
cell_width=width,
bold=bold_text,
italic=italic_text,
text_color=text_color,
)
x += width
tdi += column_span
tri += 1
y += height
total_width = x + self.margin
total_height = y + self.margin
self.svg["width"] = "{}mm".format(total_width)
self.svg["height"] = "{}mm".format(y)
self.svg["viewBox"] = "0 0 {} {}".format(total_width, y)
self.svg["height"] = "{}mm".format(total_height)
self.svg["viewBox"] = "0 0 {} {}".format(total_width, total_height)
self.svg.save(pretty=True)
def add_text(self, text, x, y):
self.svg.add(
self.svg.text(
str(text).upper(),
insert=tuple((x, y)),
**{
"font-size": 4.13,
"font-family": "OpenGost Type B TT",
"text-anchor": "start",
"alignment-baseline": "baseline",
"dominant-baseline": "hanging",
}
)
)
def get_style(self, style_name, styles):
style = styles[style_name] if style_name else {}
return style
def get_box_alignment(self, style):
if style and "vertical-align" in style and style["vertical-align"] != "automatic":
vertical_align = style["vertical-align"]
else:
vertical_align = "bottom"
alignment_translation = {
"center": "middle",
"end": "right",
"start": "left",
}
if style and "text-align" in style and style["text-align"] != "automatic":
horizontal_align = style["text-align"]
horizontal_align = alignment_translation.get(horizontal_align, horizontal_align)
else:
horizontal_align = "left"
if vertical_align == "middle" and horizontal_align == "middle":
box_alignment = "center"
else:
box_alignment = f"{vertical_align}-{horizontal_align}"
return box_alignment
def add_text(
self,
p_tags,
x,
y,
font_size,
box_alignment="bottom-left",
wrap_text=False,
cell_width=100,
bold=False,
italic=False,
text_color=None,
):
"""
Adds text to svg.
Args:
p_tags: list of cell's P tags from odt file
box_alignment: alignment of text in box
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]
box_alignment_params = SvgWriter.get_box_alignment_parameters(box_alignment)
text_params = {
"font-size": font_size,
"font-family": FONT_FAMILY,
}
if bold:
text_params["font-weight"] = "bold"
if italic:
text_params["font-style"] = "italic"
if text_color:
text_params["fill"] = text_color
if len(text_lines) == 1 and not wrap_text:
text_params.update(box_alignment_params)
text_tag = self.svg.text(text_lines[0], insert=(x, y), **(text_params))
self.svg.add(text_tag)
return
text_tag = self.svg.text("", **(text_params | {"font-size": "0"} | box_alignment_params))
# TODO: should be done in less naive way
# without using magic number for FONT_WIDTH
# currently it might not work for all fonts and font sizes
if wrap_text:
wrapped_lines = []
for line in text_lines:
wrapped_line = wrap(line, width=int(cell_width // FONT_WIDTH(font_size)), break_long_words=False)
wrapped_lines.extend(wrapped_line)
else:
wrapped_lines = text_lines
for line_number, text_line in enumerate(wrapped_lines[::-1]):
# position has to be inserted at tspan to avoid x offset between tspans
tspan = self.svg.tspan(text_line, insert=(x, y), **text_params)
# doing it here and not in tspan constructor because constructor adds unnecessary spaces
tspan.update({"dy": f"-{line_number}em"})
text_tag.add(tspan)
self.svg.add(text_tag)
def convert_to_mm(self, value):
# XSL is what defines the units of measurements in ODF
@@ -16,9 +16,23 @@
# You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
from gpu.types import GPUShader
from gpu_extras.batch import batch_for_shader
import gpu
from mathutils import Vector
# NOTES:
# Since Metal doesn't support geometry shaders we stick to builtin shaders
# and generate all geometry data in python before passing it to the shader.
# This way was considered to be the most reliable atm.
# More: https://blender.stackexchange.com/questions/291674/migrating-geometry-shaders-to-metal
#
# BGL deprecation:
# since `bgl` is deprecated, creating smoothing lines became tricky
# Notes for creating shaders with smoothed lines:
# in geom shader - use triangle_strip, DEFAULT_SETUP, do_edge_verts or do_vertex to emit vertices
# in frag shader - use lineWidth uniform, smoothline flaot in, smoothing shader code from base shader
# mind the vertex limit since emitting vertices for smoothed lines produces twice as much vertices
BASE_DEF_GLSL = """
@@ -29,12 +43,6 @@ BASE_DEF_GLSL = """
#define lineSmooth true
"""
# since `bgl` is deprecated, creating smoothing lines became tricky
# Notes for creating shaders with smoothed lines:
# in geom shader - use triangle_strip, DEFAULT_SETUP, do_edge_verts or do_vertex to emit vertices
# in frag shader - use lineWidth uniform, smoothline flaot in, smoothing shader code from base shader
# mind the vertex limit since emitting vertices for smoothed lines produces twice as much vertices
BASE_LIB_GLSL = """
// TODO: redefine as macor instead
uniform vec2 winsize;
@@ -160,12 +168,11 @@ void do_vertex_util(vec4 pos, vec2 ofs)
// geometry utils
void triangle_head(in vec4 side, in vec4 dir, in float length, in float width, in float radius, out vec4 head[5]) {
vec4 nose = side * length;
vec4 ear = dir * width;
// TODO: radius is unnecessary?
head[0] = side * -radius;
head[1] = nose * -.5;
head[2] = vec4(0) + ear;
head[3] = nose * .5;
head[1] = side * length * -.5;
head[2] = dir * width;
head[3] = side * length * .5;
head[4] = side * radius;
}
@@ -189,6 +196,22 @@ void do_circle_head(vec4 pos_w, vec4 head[CIRCLE_SEGS]) {
"""
def add_verts_sequence(verts, start_i, output_verts, output_edges, closed=False):
"""Add sequence of verts to output lists, returns next vertex index"""
for i, v in enumerate(verts[:-1], start_i):
output_verts.append(v)
output_edges.append((i, i + 1))
output_verts.append(verts[-1])
if closed:
output_edges.append((i + 1, start_i))
return i + 2
def add_offsets(v, offsets):
"""returns list of verts with offsets added"""
return [v + offset for offset in offsets]
class BaseShader:
"""Wrapper for GPUShader
To use for viewport decorations with geometry generated on GPU side.
@@ -265,48 +288,49 @@ class BaseShader:
"""
def __init__(self):
# NB: libcode arg doesn't work
# TODO: rename to .shader
self.prog = GPUShader(
vertexcode=self.VERT_GLSL,
fragcode=self.FRAG_GLSL,
geocode=self.LIB_GLSL + self.GEOM_GLSL,
defines=self.DEF_GLSL,
)
# 3D_POLYLINE_UNIFORM_COLOR is good for smoothed lines since `bgl.enable(GL_LINE_SMOOTH)` is deprecated
self.line_shader = gpu.shader.from_builtin("3D_POLYLINE_UNIFORM_COLOR")
self.base_shader = gpu.shader.from_builtin("3D_UNIFORM_COLOR")
def get_shader(self):
"""Returns shader for this type"""
return self.line_shader if self.TYPE == "LINES" else self.base_shader
def batch(self, indices=None, **data):
"""Returns automatic GPUBatch filled with provided parameters"""
batch = batch_for_shader(self.prog, self.TYPE, data, indices=indices)
batch.program_set(self.prog)
shader = self.get_shader()
batch = batch_for_shader(shader, self.TYPE, data, indices=indices)
return batch
def bind(self):
self.prog.bind()
"""need to bind shader before changing it's uniforms"""
shader = self.get_shader()
shader.bind()
return shader
def glenable(self):
gpu.state.blend_set("ALPHA")
gpu.state.depth_test_set("LESS_EQUAL")
def uniform_region(self, ctx):
shader = self.bind()
region = ctx.region
region3d = ctx.region_data
uniform_floats = {
"viewMatrix": region3d.perspective_matrix,
"winsize": (region.width, region.height),
"ModelViewProjectionMatrix": region3d.perspective_matrix,
# POLYLINE_UNIFORM_COLOR specific uniforms
"viewportSize": (region.width, region.height),
"lineWidth": 2.5,
}
for name, value in uniform_floats.items():
try:
self.prog.uniform_float(name, value)
# TODO: shouldn't just try'n'catch them
# because they may indicate errors in code
except ValueError: # unused uniform
pass
shader.uniform_float(name, value)
# TODO: add smoothing if this shades is going to be used
# TODO: add smoothing if this shader is going to be used
# TODO: dead code?
class BaseLinesShader(BaseShader):
"""Draws line segments with gaps around vertices at endpoints"""
@@ -429,37 +453,24 @@ class ExtrusionGuidesShader(GizmoShader):
TYPE = "LINES"
DEF_GLSL = (
BaseShader.DEF_GLSL
+ """
#define CROSS_SIZE .5
"""
)
def process_geometry(self, verts):
CROSS_SIZE = 0.5
GEOM_GLSL = """
uniform mat4 ModelViewProjectionMatrix;
p0, p1 = verts
bx = Vector((1, 0, 0)) * CROSS_SIZE
by = Vector((0, 1, 0)) * CROSS_SIZE
layout(lines) in;
layout(triangle_strip, max_vertices=MAX_POINTS) out;
output_verts = []
output_edges = []
out_kwargs = {
"output_verts": output_verts,
"output_edges": output_edges,
}
void main() {
// default setup for macro to work
vec2 EDGE_DIR;
start_i = 0
start_i = add_verts_sequence(add_offsets(p0, [-bx, bx]), start_i, **out_kwargs)
start_i = add_verts_sequence(add_offsets(p0, [-by, by]), start_i, **out_kwargs)
start_i = add_verts_sequence(add_offsets(p1, [-bx, bx]), start_i, **out_kwargs)
start_i = add_verts_sequence(add_offsets(p1, [-by, by]), start_i, **out_kwargs)
vec4 p0 = gl_in[0].gl_Position, p1 = gl_in[1].gl_Position;
do_edge_verts(p0, p1);
EndPrimitive();
vec4 bx = ModelViewProjectionMatrix[0] * CROSS_SIZE;
vec4 by = ModelViewProjectionMatrix[1] * CROSS_SIZE;
do_edge_verts(p0 - bx, p0 + bx);
EndPrimitive();
do_edge_verts(p0 - by, p0 + by);
EndPrimitive();
do_edge_verts(p1 - bx, p1 + bx);
EndPrimitive();
do_edge_verts(p1 - by, p1 + by);
EndPrimitive();
}
"""
return output_verts, output_edges
@@ -27,6 +27,13 @@ import xml.etree.ElementTree as ET
import blenderbim.tool as tool
import ifcopenshell.util.geolocation
from xml.dom import minidom
from mathutils import Vector
import re
VIEW_TITLE_OFFSET_Y = 5
DEFAULT_POSITION = Vector((30, 30))
SVG = "{http://www.w3.org/2000/svg}"
XLINK = "{http://www.w3.org/1999/xlink}"
class SheetBuilder:
@@ -99,25 +106,68 @@ class SheetBuilder:
view_width = self.convert_to_mm(view_root.attrib.get("width"))
view_height = self.convert_to_mm(view_root.attrib.get("height"))
# add background
if os.path.isfile(underlay_path):
background = ET.SubElement(view, "image")
background.attrib["data-type"] = "background"
background.attrib["xlink:href"] = os.path.relpath(underlay_path, layout_dir)
background.attrib["x"] = "30"
background.attrib["y"] = "30"
background.attrib["x"] = str(DEFAULT_POSITION.x)
background.attrib["y"] = str(DEFAULT_POSITION.y)
background.attrib["width"] = str(view_width)
background.attrib["height"] = str(view_height)
# add foreground
if os.path.isfile(drawing_path):
foreground = ET.SubElement(view, "image")
foreground.attrib["data-type"] = "foreground"
foreground.attrib["xlink:href"] = os.path.relpath(drawing_path, layout_dir)
foreground.attrib["x"] = "30"
foreground.attrib["y"] = "30"
foreground.attrib["x"] = str(DEFAULT_POSITION.x)
foreground.attrib["y"] = str(DEFAULT_POSITION.y)
foreground.attrib["width"] = str(view_width)
foreground.attrib["height"] = str(view_height)
self.add_view_title(30, view_height + 35, view, layout_dir)
self.add_view_title(
DEFAULT_POSITION.x, view_height + DEFAULT_POSITION.y + VIEW_TITLE_OFFSET_Y, view, layout_dir
)
layout_tree.write(layout_path)
def update_sheet_drawing_sizes(self, sheet):
ET.register_namespace("", "http://www.w3.org/2000/svg")
layout_path = tool.Drawing.get_document_uri(sheet, "LAYOUT")
layout_tree = ET.parse(layout_path)
layout_root = layout_tree.getroot()
ifc_file = tool.Ifc.get()
# iterate over all drawings in the sheet
drawings_views = layout_root.findall(f'{SVG}g[@data-type="drawing"]')
for drawing_view in drawings_views:
# find drawing in ifc file to get the drawing dimensions
drawing = ifc_file.by_guid(drawing_view.attrib.get("data-drawing"))
drawing_path = tool.Drawing.get_document_uri(tool.Drawing.get_drawing_reference(drawing))
drawing_tree = ET.parse(drawing_path)
drawing_root = drawing_tree.getroot()
view_width = self.convert_to_mm(drawing_root.attrib.get("width"))
view_height = self.convert_to_mm(drawing_root.attrib.get("height"))
foreground = drawing_view.find(f'.//{SVG}image[@data-type="foreground"]')
height = float(foreground.attrib["height"])
width = float(foreground.attrib["width"])
readjust = Vector((width - view_width, height - view_height)) / 2
for image in drawing_view.findall(f"{SVG}image"):
x = float(image.attrib["x"])
y = float(image.attrib["y"])
if image.attrib["data-type"] == "view-title":
image.attrib["x"] = str(x - readjust.x)
image.attrib["y"] = str(y - readjust.y)
else:
image.attrib["x"] = str(x + readjust.x)
image.attrib["y"] = str(y + readjust.y)
image.attrib["width"] = str(view_width)
image.attrib["height"] = str(view_height)
layout_tree.write(layout_path)
def remove_drawing(self, reference, sheet):
@@ -134,7 +184,7 @@ class SheetBuilder:
layout_tree.write(layout_path)
def add_schedule(self, reference, schedule, sheet):
def add_document(self, reference, schedule, sheet):
view_path = tool.Drawing.get_path_with_ext(tool.Drawing.get_document_uri(schedule), "svg")
if not os.path.exists(view_path):
tool.Drawing.create_svg_schedule(schedule)
@@ -161,12 +211,14 @@ class SheetBuilder:
foreground = ET.SubElement(view, "image")
foreground.attrib["data-type"] = "table"
foreground.attrib["xlink:href"] = os.path.relpath(view_path, layout_dir)
foreground.attrib["x"] = "30"
foreground.attrib["y"] = "30"
foreground.attrib["x"] = str(DEFAULT_POSITION.x)
foreground.attrib["y"] = str(DEFAULT_POSITION.y)
foreground.attrib["width"] = str(view_width)
foreground.attrib["height"] = str(view_height)
self.add_view_title(30, view_height + 35, view, layout_dir)
self.add_view_title(
DEFAULT_POSITION.x, view_height + DEFAULT_POSITION.y + VIEW_TITLE_OFFSET_Y, view, layout_dir
)
layout_tree.write(layout_path)
def add_view_title(self, x, y, parent, layout_dir):
@@ -230,8 +282,67 @@ class SheetBuilder:
titleblock.append(g)
titleblock.remove(image)
def ensure_drawing_unique_styles(self, svg, drawing_id):
"""ensures all drawing's classes and ids will be unique for the whole sheet
by adding `drawing_id` based prefix
"""
prefix = f"d{drawing_id}" # just number doesn't work
# add .prefix class to all css selectors
style = svg.find(f"{SVG}defs/{SVG}style")
style_data = style.text
text = ""
brackets_level = 0
for l in style_data:
if l == "{":
if brackets_level == 0:
cur_line = text.splitlines()[-1]
text = text[: -len(cur_line)]
css_selectors = []
# making sure cases like "text, tspan" will be
# converted to "text.prefix, tspan.prefix"
for css_selector in cur_line.split(","):
css_selector = f"{css_selector.strip()}.{prefix}"
css_selectors.append(css_selector)
text += ", ".join(css_selectors) + " "
brackets_level += 1
elif l == "}":
brackets_level -= 1
text += l
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"):
continue
attrib = svg_element.attrib
# add "prefix-" to all ids
if "id" in attrib:
attrib["id"] = f"{prefix}-{attrib['id']}"
# 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:
href = attrib[href_attrib]
if href.startswith("#"):
attrib[href_attrib] = f"#{prefix}-{href[1:]}"
return svg
def build_drawings(self, root, sheet):
for view in root.findall('{http://www.w3.org/2000/svg}g[@data-type="drawing"]'):
drawing_id = int(view.attrib["data-id"])
reference = tool.Ifc.get().by_id(int(view.attrib["data-id"]))
drawing = tool.Ifc.get().by_id(view.attrib["data-drawing"])
@@ -250,7 +361,9 @@ class SheetBuilder:
view_title = image
if foreground is not None:
view.append(self.parse_embedded_svg(foreground, {}))
svg = self.parse_embedded_svg(foreground, {})
svg = self.ensure_drawing_unique_styles(svg, drawing_id)
view.append(svg)
if background is not None:
background_path = os.path.join(self.layout_dir, self.get_href(background))
@@ -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,44 +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 = (matrix_world @ points[0].co.xyz).z
if bpy.context.scene.unit_settings.system == "IMPERIAL":
rl = helper.format_distance(rl, precision=self.precision, decimal_places=self.decimal_places)
else:
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
rl /= unit_scale
rl = ifcopenshell.util.geolocation.auto_z2e(tool.Ifc.get(), rl)
rl *= unit_scale
rl = "{:.3f}m".format(rl)
text_style = self.get_box_alignment_parameters("bottom-left")
self.svg.add(self.svg.text(f"RL +{rl}", insert=tuple(text_position), class_="SECTIONLEVEL", **text_style))
if tag:
self.svg.add(self.svg.text(tag, insert=(text_position[0], text_position[1] - 5), **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"
)
def draw_stair_annotation(self, obj):
x_offset = self.raw_width / 2
@@ -263,7 +280,7 @@ class SvgWriter:
"UP",
insert=tuple(text_position),
class_="STAIR",
**self.get_box_alignment_parameters("center"),
**SvgWriter.get_box_alignment_parameters("center"),
)
)
@@ -287,7 +304,7 @@ class SvgWriter:
)
line["stroke-dasharray"] = "12.5, 3, 3, 3"
axis_tag = tool.Ifc.get_entity(obj).Name
text_style = self.get_box_alignment_parameters("center")
text_style = SvgWriter.get_box_alignment_parameters("center")
self.svg.add(
self.svg.text(
axis_tag,
@@ -313,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)
@@ -396,8 +414,7 @@ class SvgWriter:
self.svg.line(start=start * self.svg_scale, end=end * self.svg_scale, class_=" ".join(classes))
)
# BATTING ANNOTATIONS
if predefined_type == "BATTING":
def draw_batting_annotation():
v0_global = matrix_world @ obj.data.vertices[0].co.xyz
v1_global = matrix_world @ obj.data.vertices[1].co.xyz
v0 = self.project_point_onto_camera(v0_global)
@@ -463,7 +480,99 @@ class SvgWriter:
)
self.svg.add(self.svg.polyline(points=points, class_=" ".join(classes), style=polyline_style))
elif predefined_type == "SECTION":
def draw_revision_cloud_annotation():
segment_width = 15.0
base_height = 1
width = 5
def get_svg_half_circle(height, width):
cp0 = f"0,-{height}"
cp1 = f"{width},-{height}"
end_point = f"{width},0"
circle = f"c{cp0} {cp1} {end_point}"
return circle
def get_revision_pattern(base_offset):
pattern = f"m{base_offset.x},{base_offset.y}"
pattern += " " + get_svg_half_circle(2 * base_height, width)
pattern += " " + get_svg_half_circle(2.5 * base_height, width)
pattern += " " + get_svg_half_circle(1.5 * base_height, width)
return pattern
def get_scale(size, direction):
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):
edges = []
n_verts = len(poly)
lats_index = n_verts - 1
for i in range(len(poly)):
edge = [poly[i], (poly[i + 1]) if i != lats_index else poly[0]]
edges.append(edge)
return edges
element = tool.Ifc.get_entity(obj)
safe_offset_x = 2.0
marker_width = segment_width + safe_offset_x * 2
market_height = 15.0
ref_y = 5.0
revision_pattern = get_revision_pattern(Vector([safe_offset_x, ref_y]))
bm = tool.Blender.get_bmesh_for_mesh(obj.data).copy()
bmesh.ops.contextual_create(bm, geom=bm.edges[:])
faces = bm.faces[:]
assert len(faces) == 1, "Revision cloud edges must form just 1 polygon"
# ensure clockwise order of polygon verts
# given default blender counter-clockwise order
polygon = faces[0]
if polygon.normal.z > 0:
polygon.normal_flip()
marker_id = f"revision-cloud-{element.GlobalId}"
svg_path = self.svg.path(style="fill: none; stroke:red; stroke-width:0.20", d=revision_pattern)
marker = self.svg.marker(
markerUnits="userSpaceOnUse",
insert=(safe_offset_x, ref_y),
size=(marker_width, market_height),
orient="auto",
id=marker_id,
)
marker.add(svg_path)
self.svg.add(marker)
for v0, v1 in poly_to_edges(polygon.verts):
v0_global = matrix_world @ v0.co.xyz
v1_global = matrix_world @ v1.co.xyz
v0 = self.project_point_onto_camera(v0_global)
v1 = self.project_point_onto_camera(v1_global)
start_svg = Vector(((x_offset + v0.x), (y_offset - v0.y))) * self.svg_scale
end_svg = Vector(((x_offset + v1.x), (y_offset - v1.y))) * self.svg_scale
pattern_edge = end_svg - start_svg
pattern_dir = pattern_edge.normalized()
pattern_length = pattern_edge.length
segments = ceil(pattern_length / segment_width)
pattern_dir_step = pattern_dir * segment_width
# 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; "
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
)
self.svg.add(polyline)
def draw_section_annotation():
display_data = DecoratorData.get_section_markers_display_data(obj)
connect_markers = display_data["connect_markers"]
@@ -504,6 +613,12 @@ class SvgWriter:
)
)
if predefined_type == "BATTING":
draw_batting_annotation()
elif predefined_type == "REVISIONCLOUD":
draw_revision_cloud_annotation()
elif predefined_type == "SECTION":
draw_section_annotation()
else:
for edge in obj.data.edges:
draw_simple_edge_annotation(*edge.vertices[:])
@@ -557,7 +672,7 @@ class SvgWriter:
reference_id, sheet_id = self.get_reference_and_sheet_id_from_annotation(tool.Ifc.get_entity(obj))
text_position = symbol_position_svg
text_style = self.get_box_alignment_parameters("center")
text_style = SvgWriter.get_box_alignment_parameters("center")
self.svg.add(
self.svg.text(
reference_id,
@@ -590,7 +705,7 @@ class SvgWriter:
reference_id, sheet_id = self.get_reference_and_sheet_id_from_annotation(tool.Ifc.get_entity(obj))
text_position = symbol_position_svg
text_style = self.get_box_alignment_parameters("center")
text_style = SvgWriter.get_box_alignment_parameters("center")
self.svg.add(
self.svg.text(
reference_id, insert=(text_position[0], text_position[1] - 2.5), class_="ELEVATION", **text_style
@@ -606,18 +721,23 @@ 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 ("-", "-")
def get_box_alignment_parameters(self, box_alignment):
@staticmethod
def get_box_alignment_parameters(box_alignment):
"""Convenience method to get svg parameters for text alignment
in a readable way.
@@ -664,6 +784,7 @@ class SvgWriter:
text_position = self.project_point_onto_camera(position)
text_position = Vector(((x_offset + text_position.x), (y_offset - text_position.y)))
text_position_svg = text_position * self.svg_scale
text_position_svg_str = ", ".join(map(str, text_position_svg))
def get_basis_vector(matrix, i=0):
"""returns basis vector for i in world space, unaffected by object scale"""
@@ -673,13 +794,18 @@ class SvgWriter:
text_dir = (self.camera.matrix_world.inverted().to_quaternion() @ text_dir_world_x_axis).to_2d().normalized()
angle = math.degrees(-text_dir.angle_signed(Vector((1, 0))))
transform = "rotate({}, {}, {})".format(angle, *text_position_svg)
classes = self.get_attribute_classes(text_obj)
classes_str = " ".join(classes)
symbol = tool.Drawing.get_annotation_symbol(element)
template_text_fields = []
if symbol:
if not symbol:
text_transform = f"translate({text_position_svg_str}) rotate({angle})"
else:
# NOTE: for now we assume that scale is uniform
symbol_transform = f"translate({text_position_svg_str}) rotate({angle}) scale({text_obj.scale.x})"
text_transform = symbol_transform
symbol_svg = self.find_xml_symbol_by_id(symbol)
if symbol_svg:
symbol_xml = symbol_svg.get_xml()
@@ -687,7 +813,7 @@ class SvgWriter:
# if there is a symbol with template text fields
# then we just populate it's fields with the data from text literals
if template_text_fields:
symbol_xml.attrib["transform"] = f"translate({', '.join(map(str, text_position_svg))})"
symbol_xml.attrib["transform"] = symbol_transform
symbol_xml.attrib.pop("id")
# note: zip makes sure that we iterate over the shortest list
for field, text_literal in zip(template_text_fields, text_literals):
@@ -697,41 +823,43 @@ class SvgWriter:
return None
if not symbol_svg or not template_text_fields:
self.svg.add(self.svg.use(f"#{symbol}", insert=text_position_svg))
self.svg.add(self.svg.use(f"#{symbol}", transform=symbol_transform))
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": 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):
text_tag = self.svg.text(
"",
**(attribs | {"filter": "url(#fill-background)"}) if add_fill_bg else attribs,
**self.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 line_number, text_line in enumerate(text_lines):
# position has to be inserted at tspan to avoid x offset between tspans
tspan = self.svg.tspan(text_line, class_=classes_str, insert=text_position_svg)
# doing it here and not in tspan constructor because constructor adds unnecessary spaces
tspan.update({"dy": f"{line_number}em"})
text_tag.add(tspan)
element = tool.Ifc.get_entity(obj)
svg_id = str(ifcopenshell.util.element.get_predefined_type(element))
if "fill-bg" in classes:
add_text_tag(True)
add_text_tag(False)
# 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}"
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
@@ -757,53 +885,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) - 2.5,
)
)
# TODO: allow metric to be configurable
rl = (matrix_world @ points[0].co).z
if bpy.context.scene.unit_settings.system == "IMPERIAL":
rl = helper.format_distance(rl, 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 /= 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 = self.get_box_alignment_parameters(box_alignment)
self.svg.add(
self.svg.text(
"RL +{}".format(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:
@@ -824,7 +959,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()
@@ -912,7 +1047,7 @@ class SvgWriter:
text_offset = (text_position - center_position).xy.normalized() * 5
text_position += text_offset
text_style = self.get_box_alignment_parameters("center")
text_style = SvgWriter.get_box_alignment_parameters("center")
angle_text = abs(round(math.degrees(angle), 3))
if is_reflex:
angle_text = 360 - angle_text
@@ -937,48 +1072,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 = self.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
@@ -1046,7 +1194,7 @@ class SvgWriter:
)
text_position += text_offset
text_style = self.get_box_alignment_parameters("center")
text_style = SvgWriter.get_box_alignment_parameters("center")
self.svg.add(self.svg.text(tag, insert=tuple(text_position), class_="RADIUS", **text_style))
def draw_diameter_annotations(self, obj):
@@ -1071,6 +1219,9 @@ class SvgWriter:
text_format=lambda x: "D" + x,
show_description_only=dimension_data["show_description_only"],
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):
@@ -1092,6 +1243,9 @@ class SvgWriter:
dimension_text=dimension_text,
show_description_only=dimension_data["show_description_only"],
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):
@@ -1115,6 +1269,9 @@ class SvgWriter:
text_format=lambda x: x,
show_description_only=False,
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)
@@ -1124,38 +1281,118 @@ 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
text_position = mid if sheet_dimension > 5 else (end + (3 * vector.normalized()))
rotation = math.degrees(vector.angle_signed(Vector((1, 0))))
angle = math.degrees(vector.angle_signed(Vector((1, 0))))
line = self.svg.line(start=start, end=end, class_=" ".join(classes))
self.svg.add(line)
def create_text_tag(text, text_position, box_alignment):
text_kwargs = {"transform": "rotate({} {} {})".format(rotation, text_position.x, text_position.y)}
return self.svg.text(
text_format(text),
insert=text_position,
class_="DIMENSION",
**(text_kwargs | self.get_box_alignment_parameters(box_alignment)),
)
text_tags = []
text_tag_kwargs = {
"angle": angle,
"class_str": "DIMENSION",
"text_format": text_format,
"fill_bg": fill_bg,
}
if not show_description_only:
self.svg.add(create_text_tag(str(dimension), text_position + perpendicular, "bottom-middle"))
if dimension_text:
self.svg.add(create_text_tag(dimension_text, text_position - perpendicular, "top-middle"))
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,
)
text = text_prefix + str(dimension) + text_suffix
else:
if not dimension_text:
return
text = dimension_text
elif show_description_only and dimension_text:
self.svg.add(create_text_tag(dimension_text, text_position + perpendicular, "bottom-middle"))
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=0.0,
box_alignment="bottom-left",
class_str="",
text_format=lambda x: x,
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:
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,
**(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, **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, 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_tags
def project_point_onto_camera(self, point):
# TODO is this needlessly complex?
+191 -157
View File
@@ -23,10 +23,11 @@ from bpy.types import Panel
from blenderbim.bim.module.drawing.data import (
ProductAssignmentsData,
SheetsData,
SchedulesData,
DocumentsData,
DrawingsData,
DecoratorData,
)
from blenderbim.bim.module.drawing.prop import ANNOTATION_TYPES_DATA
class BIM_PT_camera(Panel):
@@ -70,22 +71,19 @@ class BIM_PT_camera(Panel):
row = layout.row()
row.prop(dprops, "should_extract")
row = layout.row()
row.prop(props, "is_nts")
row = layout.row()
row.operator("bim.resize_text")
row = layout.row()
row.prop(props, "raster_x")
row = layout.row()
row.prop(props, "raster_y")
row = layout.row()
row = layout.row(align=True)
row.prop(props, "diagram_scale")
row.prop(props, "is_nts", text="", icon="MOD_EDGESPLIT")
if props.diagram_scale == "CUSTOM":
row = layout.row()
row.prop(props, "custom_diagram_scale")
row = layout.row(align=True)
row.prop(props, "custom_scale_numerator", text="Custom Scale")
row.prop(props, "custom_scale_denominator", text="")
row = layout.row(align=True)
row.operator("bim.create_drawing", text="Create Drawing", icon="OUTPUT")
@@ -111,61 +109,72 @@ class BIM_PT_drawing_underlay(Panel):
layout.use_property_split = True
dprops = context.scene.DocProperties
props = context.active_object.data.BIMCameraProperties
drawing_index_is_valid = props.active_drawing_style_index < len(dprops.drawing_styles)
if not DrawingsData.is_loaded:
DrawingsData.load()
drawing_pset_data = DrawingsData.data["active_drawing_pset_data"]
row = layout.row(align=True)
row.operator("bim.add_drawing_style")
current_shading_style = drawing_pset_data.get("CurrentShadingStyle", None)
if current_shading_style is None:
row.label(text="Current style is not set.")
else:
row.label(text="Current Shading Style:")
row.label(text=current_shading_style)
row.operator("bim.add_drawing_style", icon="ADD", text="")
if drawing_index_is_valid:
row.operator("bim.remove_drawing_style", icon="X", text="").index = props.active_drawing_style_index
row.operator("bim.reload_drawing_styles", icon="FILE_REFRESH", text="")
if dprops.drawing_styles:
layout.template_list("BIM_UL_generic", "", dprops, "drawing_styles", props, "active_drawing_style_index")
if not dprops.drawing_styles:
return
layout.template_list("BIM_UL_generic", "", dprops, "drawing_styles", props, "active_drawing_style_index")
if props.active_drawing_style_index < len(dprops.drawing_styles):
drawing_style = dprops.drawing_styles[props.active_drawing_style_index]
if not drawing_index_is_valid:
return
drawing_style = dprops.drawing_styles[props.active_drawing_style_index]
row = layout.row(align=True)
row.prop(drawing_style, "name")
row.operator("bim.remove_drawing_style", icon="X", text="").index = props.active_drawing_style_index
row = layout.row(align=True)
row.prop(drawing_style, "name")
row = layout.row()
row.prop(drawing_style, "render_type")
row = layout.row(align=True)
row.prop(drawing_style, "include_query")
row = layout.row(align=True)
row.prop(drawing_style, "exclude_query")
row = layout.row()
row.prop(drawing_style, "render_type")
row = layout.row(align=True)
row.prop(drawing_style, "include_query")
row = layout.row(align=True)
row.prop(drawing_style, "exclude_query")
row = layout.row()
row.operator("bim.add_drawing_style_attribute")
row = layout.row()
row.operator("bim.add_drawing_style_attribute")
for index, attribute in enumerate(drawing_style.attributes):
row = layout.row(align=True)
row.prop(attribute, "name", text="")
row.operator("bim.remove_drawing_style_attribute", icon="X", text="").index = index
for index, attribute in enumerate(drawing_style.attributes):
row = layout.row(align=True)
row.prop(attribute, "name", text="")
row.operator("bim.remove_drawing_style_attribute", icon="X", text="").index = index
row = layout.row(align=True)
row.operator("bim.save_drawing_style")
row.operator("bim.activate_drawing_style")
row = layout.row(align=True)
row.operator("bim.save_drawing_style")
row.operator("bim.activate_drawing_style")
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:
DrawingsData.load()
if not DrawingsData.data["has_saved_ifc"]:
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
draw_project_not_saved_ui(self)
return
self.props = context.scene.DocProperties
@@ -190,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"
)
@@ -220,31 +242,27 @@ 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 SchedulesData.is_loaded:
SchedulesData.load()
if not DocumentsData.is_loaded:
DocumentsData.load()
if not SchedulesData.data["has_saved_ifc"]:
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
if not DocumentsData.data["has_saved_ifc"]:
draw_project_not_saved_ui(self)
return
self.props = context.scene.DocProperties
if not self.props.is_editing_schedules:
row = self.layout.row(align=True)
row.label(text=f"{SchedulesData.data['total_schedules']} Schedules Found", icon="LONGDISPLAY")
row.label(text=f"{DocumentsData.data['total_schedules']} Schedules Found", icon="LONGDISPLAY")
row.operator("bim.load_schedules", text="", icon="IMPORT")
return
@@ -268,27 +286,72 @@ class BIM_PT_schedules(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"
def draw_project_not_saved_ui(self):
row = self.layout.row()
row.label(text="Project Not Yet Saved", icon="ERROR")
class BIM_PT_references(Panel):
bl_label = "References"
bl_idname = "BIM_PT_references"
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:
DocumentsData.load()
if not DocumentsData.data["has_saved_ifc"]:
draw_project_not_saved_ui(self)
return
self.props = context.scene.DocProperties
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.operator("bim.load_references", text="", icon="IMPORT")
return
row = self.layout.row(align=True)
row.operator("bim.add_reference", icon="ADD")
row.operator("bim.disable_editing_references", text="", icon="CANCEL")
if self.props.references:
if self.props.active_reference_index < len(self.props.references):
active_reference = self.props.references[self.props.active_reference_index]
row = self.layout.row(align=True)
row.alignment = "RIGHT"
row.operator("bim.open_reference", icon="URL", text="").reference = active_reference.ifc_definition_id
row.operator("bim.remove_reference", icon="X", text="").reference = active_reference.ifc_definition_id
self.layout.template_list(
"BIM_UL_generic", "", self.props, "references", self.props, "active_reference_index"
)
class BIM_PT_sheets(Panel):
bl_label = "Sheets"
bl_idname = "BIM_PT_sheets"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
@classmethod
def poll(cls, context):
return tool.Blender.is_tab(context, "DRAWINGS") and tool.Ifc.get()
def draw(self, context):
if not SheetsData.is_loaded:
SheetsData.load()
if not SheetsData.data["has_saved_ifc"]:
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
draw_project_not_saved_ui(self)
return
self.props = context.scene.DocProperties
@@ -312,6 +375,7 @@ class BIM_PT_sheets(Panel):
row.operator("bim.open_sheet", icon="URL", text="")
row.operator("bim.add_drawing_to_sheet", icon="IMAGE_PLANE", text="")
row.operator("bim.add_schedule_to_sheet", icon="PRESET_NEW", text="")
row.operator("bim.add_reference_to_sheet", icon="IMAGE_REFERENCE", text="")
row.operator("bim.create_sheets", icon="FILE_REFRESH", text="")
if active_sheet.is_sheet:
row.operator("bim.remove_sheet", icon="X", text="").sheet = active_sheet.ifc_definition_id
@@ -323,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):
@@ -358,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):
@@ -435,92 +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 = "curve"
op = row.operator("bim.add_annotation", text="Angle", icon="DRIVER_ROTATIONAL_DIFFERENCE")
op.object_type = "ANGLE"
op.data_type = "curve"
row = layout.row(align=True)
op = row.operator("bim.add_annotation", text="Radius", icon="FORWARD")
op.object_type = "RADIUS"
op.data_type = "curve"
op = row.operator("bim.add_annotation", text="Diameter", icon="ARROW_LEFTRIGHT")
op.object_type = "DIAMETER"
op.data_type = "curve"
row = layout.row(align=True)
op = row.operator("bim.add_annotation", text="Text", icon="SMALL_CAPS")
op.object_type = "TEXT"
op.data_type = "empty"
op = row.operator("bim.add_annotation", text="Leader", icon="TRACKING_BACKWARDS")
op.object_type = "TEXT_LEADER"
op.data_type = "curve"
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 = "curve"
op = row.operator("bim.add_annotation", text="Hidden", icon="CON_TRACKTO")
op.object_type = "HIDDEN_LINE"
op.data_type = "mesh"
row = layout.row(align=True)
op = row.operator("bim.add_annotation", text="Level (Plan)", icon="SORTBYEXT")
op.object_type = "PLAN_LEVEL"
op.data_type = "curve"
op = row.operator("bim.add_annotation", text="Level (Section)", icon="TRIA_DOWN")
op.object_type = "SECTION_LEVEL"
op.data_type = "curve"
row = layout.row(align=True)
op = row.operator("bim.add_annotation", text="Breakline", icon="FCURVE")
op.object_type = "BREAKLINE"
op.data_type = "mesh"
op = row.operator("bim.add_annotation", text="Line", icon="MESH_MONKEY")
op.object_type = "LINEWORK"
op.data_type = "mesh"
row = layout.row(align=True)
op = row.operator("bim.add_annotation", text="Batting", icon="FORCE_FORCE")
op.object_type = "BATTING"
op.data_type = "mesh"
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 = "curve"
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"
@@ -528,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):
@@ -561,9 +565,39 @@ class BIM_UL_sheets(bpy.types.UIList):
row.label(text="", icon="MENU_PANEL")
elif item.reference_type == "REVISION":
row.label(text="", icon="RECOVER_LAST")
elif item.reference_type == "REFERENCE":
row.label(text="", icon="IMAGE_REFERENCE")
if item.identification:
name = f"{item.identification} - {item.name or 'Unnamed'}"
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"
@@ -47,7 +118,6 @@ class AnnotationTool(WorkSpaceTool):
bl_description = "Gives you Annotation related superpowers"
bl_icon = os.path.join(os.path.dirname(__file__), "ops.authoring.annotation")
bl_widget = None
# https://docs.blender.org/api/current/bpy.types.KeyMapItems.html
bl_keymap = tool.Blender.get_default_selection_keypmap() + (
("bim.annotation_hotkey", {"type": "A", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_A")]}),
("bim.annotation_hotkey", {"type": "C", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_C")]}),
@@ -91,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))
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
@@ -133,7 +169,7 @@ def create_annotation_occurence(context):
drawing = tool.Ifc.get_entity(context.scene.camera)
obj = tool.Drawing.create_annotation_object(drawing, object_type)
obj.name = relating_type.Name
ifc_context = tool.Drawing.get_annotation_context(tool.Drawing.get_drawing_target_view(drawing))
ifc_context = tool.Drawing.get_annotation_context(tool.Drawing.get_drawing_target_view(drawing), object_type)
relating_type_repr = tool.Drawing.get_annotation_representation(relating_type)
element = tool.Drawing.run_root_assign_class(
obj=obj,
@@ -153,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])
@@ -172,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()
@@ -179,29 +219,18 @@ class AnnotationToolUI:
if context.active_object and context.selected_objects:
cls.draw_edit_object_interface(context)
elif not context.selected_objects:
cls.draw_create_object_interface()
cls.draw_create_object_interface()
@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
def draw_header_interface(cls):
cls.draw_type_selection_interface()
@classmethod
def draw_basic_annotation_tool_interface(cls):
cls.draw_type_selection_interface()
@classmethod
def draw_type_selection_interface(cls):
# shared by both sidebar and header
@@ -214,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):
@@ -260,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):
@@ -285,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
@@ -104,24 +104,50 @@ class ConnectionsData:
def connections(cls):
results = []
element = tool.Ifc.get_entity(bpy.context.active_object)
for rel in getattr(element, "ConnectedTo", []):
connected_to = getattr(element, "ConnectedTo", [])
connected_from = getattr(element, "ConnectedFrom", [])
for rel in connected_to:
if element.is_a("IfcDistributionPort"):
related_element = rel.RelatedPort
else:
related_element = rel.RelatedElement
if element.is_a("IfcRelConnectsPathElements"):
related_element_connection_type = rel.RelatedConnectionType
else:
related_element_connection_type = ""
results.append(
{
"id": rel.id(),
"is_relating": True,
"Name": rel.RelatedElement.Name or "Unnamed",
"ConnectionType": rel.RelatingConnectionType,
"Name": related_element.Name or "Unnamed",
"ConnectionType": related_element_connection_type,
}
)
for rel in getattr(element, "ConnectedFrom", []):
for rel in connected_from:
if element.is_a("IfcDistributionPort"):
relating_element = rel.RelatingPort
else:
relating_element = rel.RelatingElement
if element.is_a("IfcRelConnectsPathElements"):
relating_element_connection_type = rel.RelatingConnectionType
else:
relating_element_connection_type = ""
results.append(
{
"id": rel.id(),
"is_relating": False,
"Name": rel.RelatingElement.Name or "Unnamed",
"ConnectionType": rel.RelatedConnectionType,
"Name": relating_element.Name or "Unnamed",
"ConnectionType": relating_element_connection_type,
}
)
return results
@@ -152,10 +178,18 @@ class DerivedPlacementsData:
@classmethod
def load_collection(cls):
cls.collection = bpy.data.objects.get(bpy.context.active_object.users_collection[0].name)
cls.collection = None
cls.collection_z = 0
if cls.collection:
cls.collection_z = cls.collection.matrix_world.translation.z
element = tool.Ifc.get_entity(bpy.context.active_object)
if not element:
return
parent = ifcopenshell.util.element.get_aggregate(element)
if not parent:
parent = ifcopenshell.util.element.get_container(element)
if parent:
cls.collection = tool.Ifc.get_object(parent)
if cls.collection:
cls.collection_z = cls.collection.matrix_world.translation.z
@classmethod
def has_collection(cls):
@@ -38,7 +38,7 @@ class Helper:
bm = bmesh.new()
bm.from_mesh(mesh)
bmesh.ops.dissolve_limit(bm, angle_limit=pi / 180 * 1, verts=bm.verts, edges=bm.edges)
bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=0.001)
bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=0.0001)
bm.faces.ensure_lookup_table()
face = None
@@ -62,7 +62,7 @@ class Helper:
bm = bmesh.new()
bm.from_mesh(mesh)
bmesh.ops.dissolve_limit(bm, angle_limit=pi / 180 * 1, verts=bm.verts, edges=bm.edges)
bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=0.001)
bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=0.0001)
bm.faces.ensure_lookup_table()
potential_faces = []
@@ -90,7 +90,7 @@ class Helper:
bm = bmesh.new()
bm.from_mesh(mesh)
bmesh.ops.dissolve_limit(bm, angle_limit=pi / 180 * 1, verts=bm.verts, edges=bm.edges)
bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=0.001)
bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=0.0001)
bm.faces.ensure_lookup_table()
potential_faces = []
@@ -292,7 +292,7 @@ class Helper:
bm = bmesh.new()
bm.from_mesh(mesh)
bmesh.ops.dissolve_limit(bm, angle_limit=pi / 180 * 1, verts=bm.verts, edges=bm.edges)
bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=0.001)
bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=0.0001)
bm.faces.ensure_lookup_table()
potential_faces = []
@@ -389,7 +389,7 @@ class Helper:
bm = bmesh.new()
bm.from_mesh(mesh)
bmesh.ops.dissolve_limit(bm, angle_limit=pi / 180 * 1, verts=bm.verts, edges=bm.edges)
bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=0.001)
bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=0.0001)
bm.faces.ensure_lookup_table()
faces = bm.faces
@@ -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"
@@ -199,6 +237,13 @@ class UpdateRepresentation(bpy.types.Operator, Operator):
old_representation = self.file.by_id(obj.data.BIMMeshProperties.ifc_definition_id)
context_of_items = old_representation.ContextOfItems
# TODO: remove this code a bit later
# added this as a fallback for easier transition some annotation types to 3d
# if they were create before as 2d
element = tool.Ifc.get_entity(obj)
if tool.Drawing.is_annotation_object_type(element, ("FALL", "SECTION_LEVEL", "PLAN_LEVEL")):
context_of_items = tool.Drawing.get_annotation_context("MODEL_VIEW")
gprop = context.scene.BIMGeoreferenceProperties
coordinate_offset = None
if gprop.has_blender_offset and obj.BIMObjectProperties.blender_offset_type == "CARTESIAN_POINT":
@@ -366,6 +411,7 @@ class OverrideDelete(bpy.types.Operator):
bl_options = {"REGISTER", "UNDO"}
use_global: bpy.props.BoolProperty(default=False)
confirm: bpy.props.BoolProperty(default=True)
is_batch: bpy.props.BoolProperty(name="Is Batch", default=False)
@classmethod
def poll(cls, context):
@@ -373,7 +419,7 @@ class OverrideDelete(bpy.types.Operator):
def execute(self, context):
# Deep magick from the dawn of time
if IfcStore.get_file():
if tool.Ifc.get():
return IfcStore.execute_ifc_operator(self, context)
for obj in context.selected_objects:
bpy.data.objects.remove(obj)
@@ -382,27 +428,66 @@ class OverrideDelete(bpy.types.Operator):
return {"FINISHED"}
def invoke(self, context, event):
if self.confirm:
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)
elif self.confirm:
return context.window_manager.invoke_confirm(self, event)
elif self.confirm:
return context.window_manager.invoke_confirm(self, event)
self.confirm = True
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())
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)
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)
# Required otherwise gizmos are still visible
context.view_layer.objects.active = None
return {"FINISHED"}
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 OverrideOutlinerDelete(bpy.types.Operator):
bl_idname = "bim.override_outliner_delete"
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):
@@ -435,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:
@@ -448,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):
@@ -464,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"
@@ -510,6 +634,9 @@ 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.
new_obj = obj.copy()
if obj.data:
new_obj.data = obj.data.copy()
@@ -519,6 +646,8 @@ class OverrideDuplicateMove(bpy.types.Operator):
collection.objects.link(new_obj)
obj.select_set(False)
new_obj.select_set(True)
# clear object's collection so it will be able to have it's own
new_obj.BIMObjectProperties.collection = None
# Copy the actual class
new = blenderbim.core.root.copy_class(tool.Ifc, tool.Collector, tool.Geometry, tool.Root, obj=new_obj)
if new:
@@ -527,6 +656,8 @@ class OverrideDuplicateMove(bpy.types.Operator):
array_pset = tool.Ifc.get().by_id(array_pset["id"])
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), product=new, pset=array_pset)
old_to_new[tool.Ifc.get_entity(obj)] = [new]
if new.is_a("IfcRelSpaceBoundary"):
tool.Boundary.decorate_boundary(new_obj)
# Recreate decompositions
tool.Root.recreate_decompositions(relationships, old_to_new)
blenderbim.bim.handler.purge_module_data()
@@ -597,6 +728,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"
@@ -707,6 +1274,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"}
@@ -715,31 +1283,68 @@ 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:
context.active_object.select_set(True)
for obj in objs:
element = tool.Ifc.get_entity(context.active_object)
if element and element.is_a("IfcRelSpaceBoundary"):
return bpy.ops.bim.enable_editing_boundary_geometry()
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:
if usage_type is not None and usage_type not in ("PROFILE", "LAYER3"):
# Parametric objects shall not be edited as meshes as they
# can be modified to be incompatible with the parametric
# constraints.
obj.select_set(False)
continue
representation = tool.Geometry.get_active_representation(obj)
if not representation:
continue
if tool.Blender.Modifier.is_modifier_with_non_editable_path(element):
obj.select_set(False)
continue
is_profile = True
if usage_type == "PROFILE":
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)
):
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.")
obj.select_set(False)
continue
if tool.Geometry.is_meshlike(representation):
if getattr(element, "HasOpenings", None):
@@ -755,30 +1360,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)
@@ -826,6 +1435,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
@@ -834,6 +1446,11 @@ class OverrideModeSetObject(bpy.types.Operator):
if not tool.Ifc.get():
return {"FINISHED"}
if context.active_object:
element = tool.Ifc.get_entity(context.active_object)
if element and element.is_a("IfcRelSpaceBoundary"):
return bpy.ops.bim.edit_boundary_geometry()
objs = context.selected_objects or [context.active_object]
self.edited_objs = []
@@ -847,7 +1464,22 @@ class OverrideModeSetObject(bpy.types.Operator):
if not element:
continue
if obj.data.BIMMeshProperties.ifc_definition_id:
if tool.Profile.is_editing_profile():
profile_id = context.scene.BIMProfileProperties.active_profile_id
if profile_id:
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.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:
bpy.ops.bim.edit_extrusion_profile()
return self.execute(context)
elif obj.data.BIMMeshProperties.ifc_definition_id:
if not tool.Geometry.has_geometric_data(obj):
self.is_valid = False
self.should_save = False
@@ -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):

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