Compare commits

...

557 Commits

Author SHA1 Message Date
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
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
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
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
414 changed files with 23373 additions and 9300 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'
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
+4 -1
View File
@@ -1,9 +1,12 @@
IfcOpenShell
============
<p align="center">
<img src="https://github.com/IfcOpenShell/IfcOpenShell/assets/79010126/9e540403-c79f-446a-88c6-7c1ebc6ba864">
</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
+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'
+111 -101
View File
@@ -36,12 +36,8 @@ endif
endif
VERSION:=`date '+%y%m%d'`
PYVERSION:=py37
PYVERSION:=py310
ifeq ($(PYVERSION), py37)
PYLIBDIR:=python3.7
PYNUMBER:=37
endif
ifeq ($(PYVERSION), py39)
PYLIBDIR:=python3.9
PYNUMBER:=39
@@ -52,123 +48,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 +163,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-8f41ae0-$(PLATFORM)64.zip
cd dist/working && unzip ifcopenshell-python*
cp -r dist/working/ifcopenshell dist/blenderbim/libs/site/packages/
@@ -206,9 +179,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 +379,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 +397,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 +417,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 +471,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 +489,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 +526,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 +647,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,6 +667,13 @@ 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 && zip -r blenderbim-$(VERSION)-$(PYVERSION)-$(PLATFORM).zip ./*
rm -rf dist/blenderbim
+2 -1
View File
@@ -34,7 +34,8 @@ bl_info = {
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
@@ -95,6 +95,8 @@ classes = [
operator.SelectIfcFile,
operator.ReloadSelectedIfcFile,
operator.SelectSchemaDir,
operator.FileAssociate,
operator.FileUnassociate,
operator.SelectURIAttribute,
operator.EditBlenderCollection,
operator.BIM_OT_open_webbrowser,
@@ -109,6 +111,7 @@ classes = [
prop.PsetQto,
prop.GlobalId,
prop.BIMObjectProperties,
prop.BIMCollectionProperties,
prop.BIMMaterialProperties,
prop.BIMMeshProperties,
ui.BIM_PT_section_plane,
@@ -156,6 +159,7 @@ def register():
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.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)
@@ -182,6 +186,7 @@ def unregister():
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
@@ -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": "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": 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": "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.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
}
}
}
@@ -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>
File diff suppressed because it is too large Load Diff
@@ -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;
@@ -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;
+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:
+76 -86
View File
@@ -22,19 +22,18 @@ 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
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]
@@ -75,19 +74,8 @@ def name_callback(obj, data):
element = IfcStore.get_file().by_id(obj.BIMObjectProperties.ifc_definition_id)
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 +87,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 +168,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
@@ -224,6 +262,12 @@ 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"
@persistent
def setDefaultProperties(scene):
global global_subscription_owner
@@ -231,75 +275,21 @@ def setDefaultProperties(scene):
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")
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 = {}
AuthoringData.type_thumbnails = {}
+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
+5 -1
View File
@@ -120,6 +120,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 +132,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
+160 -276
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):
@@ -178,11 +178,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 = {}
@@ -342,7 +347,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 +362,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 +384,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 +412,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.Pset.get_element_pset(element, "BBIM_Railing"):
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.Pset.get_element_pset(element, "BBIM_Railing"):
return False
return True
return False
@@ -568,6 +580,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 +627,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)
@@ -682,29 +699,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 +846,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):
@@ -862,40 +937,6 @@ class IfcImporter:
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
@@ -1298,7 +1339,6 @@ class IfcImporter:
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)
@@ -1333,39 +1373,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 []:
@@ -1408,7 +1452,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 +1538,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 +1569,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 +1585,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 +1615,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 +1624,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
@@ -1903,6 +1784,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,6 +1861,7 @@ 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_cache = True
@@ -2008,6 +1891,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
@@ -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
@@ -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"
@@ -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")
@@ -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
@@ -376,3 +400,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.col[3][:3] = 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.col[3][:3]
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)
@@ -33,6 +33,9 @@ classes = (
operator.RewindBrickClass,
operator.ViewBrickClass,
operator.ViewBrickItem,
operator.UndoBrick,
operator.RedoBrick,
operator.SerializeBrick,
prop.Brick,
prop.BIMBrickProperties,
ui.BIM_PT_brickschema,
@@ -110,7 +110,8 @@ class BrickschemaData:
return []
results = []
for alias, uri in BrickStore.graph.namespaces():
results.append((uri, f"{alias}: {uri}", ""))
# results.append((uri, f"{alias}: {uri}", ""))
results.append((uri, f"{alias}", ""))
return results
@classmethod
@@ -189,3 +189,24 @@ 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 UndoBrick(bpy.types.Operator, Operator):
bl_idname = "bim.undo_brick"
bl_label = "Undo Brick"
def _execute(self, context):
core.undo_brick(tool.Brick)
class RedoBrick(bpy.types.Operator, Operator):
bl_idname = "bim.redo_brick"
bl_label = "Redo Brick"
def _execute(self, context):
core.redo_brick(tool.Brick)
class SerializeBrick(bpy.types.Operator, Operator):
bl_idname = "bim.serialize_brick"
bl_label = "Serialize Brick"
def _execute(self, context):
core.serialize_brick(tool.Brick)
@@ -58,6 +58,13 @@ class BIM_PT_brickschema(Panel):
row.operator("bim.add_brick_feed", text="", icon="PLUGIN")
row.operator("bim.remove_brick", text="", icon="X")
row = self.layout.row(align=True)
row.operator("bim.undo_brick", icon="LOOP_BACK")
row.operator("bim.redo_brick", icon="LOOP_FORWARDS")
row = self.layout.row(align=True)
row.operator("bim.serialize_brick")
self.layout.template_list("BIM_UL_bricks", "", self.props, "bricks", self.props, "active_brick_index")
for attribute in BrickschemaData.data["attributes"]:
@@ -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")
@@ -255,28 +259,30 @@ 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 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 (
(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 (
(RoofData.is_loaded or not RoofData.load())
and RoofData.data["parameters"]
and bpy.context.active_object.BIMRoofProperties.is_editing_path
):
bpy.ops.bim.finish_editing_roof_path()
def hotkey_S_R(self):
if self.is_profile():
@@ -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
@@ -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"
)
+115 -53
View File
@@ -43,49 +43,68 @@ 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 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")
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 +113,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 +134,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",
"",
@@ -579,7 +621,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 +633,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 +658,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 +719,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 +738,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 +755,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",
@@ -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")
+16 -21
View File
@@ -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")
@@ -30,6 +30,7 @@ classes = (
operator.AddDrawingToSheet,
operator.AddSchedule,
operator.AddScheduleToSheet,
operator.AddReferenceToSheet,
operator.AddSheet,
operator.AddTextLiteral,
operator.BuildSchedule,
@@ -61,16 +62,23 @@ classes = (
operator.RemoveDrawingStyle,
operator.RemoveDrawingStyleAttribute,
operator.RemoveSchedule,
operator.ReloadDrawingStyles,
operator.RemoveSheet,
operator.RemoveTextLiteral,
operator.SelectAllDrawings,
operator.ResizeText,
operator.SaveDrawingStyle,
operator.SaveDrawingStylesData,
operator.SelectAssignedProduct,
operator.SelectDocIfcFile,
operator.LoadReferences,
operator.DisableEditingReferences,
operator.AddReference,
operator.RemoveReference,
operator.OpenReference,
prop.Variable,
prop.Drawing,
prop.Schedule,
prop.Document,
prop.DrawingStyle,
prop.Sheet,
prop.DocProperties,
@@ -85,6 +93,7 @@ classes = (
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,
@@ -100,7 +109,7 @@ classes = (
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=False, group=True)
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)
@@ -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,7 +283,7 @@ 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
@@ -279,11 +307,15 @@ class DecoratorData:
pset_data = ifcopenshell.util.element.get_pset(element, "BBIM_Dimension") or {}
show_description_only = pset_data.get("ShowDescriptionOnly", False)
suppress_zero_inches = pset_data.get("SuppressZeroInches", False)
text_prefix = pset_data.get("TextPrefix", "")
text_suffix = pset_data.get("TextSuffix", "")
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,
}
cls.data[obj.name] = dimension_data
return dimension_data
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")
@@ -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"""
@@ -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"""
@@ -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:
@@ -268,7 +269,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
@@ -41,12 +41,12 @@ import blenderbim.bim.module.drawing.sheeter as sheeter
import blenderbim.bim.module.drawing.scheduler as scheduler
import blenderbim.bim.module.drawing.helper as helper
from blenderbim.bim.module.drawing.decoration import CutDecorator
from blenderbim.bim.module.drawing.data import DecoratorData
from blenderbim.bim.module.drawing.data import DecoratorData, DrawingsData
import blenderbim.bim.export_ifc
from lxml import etree
from mathutils import Vector
from mathutils import Vector, Color
from timeit import default_timer as timer
from blenderbim.bim.module.drawing.prop import RasterStyleProperty, Literal
from blenderbim.bim.module.drawing.prop import RasterStyleProperty, Literal, RASTER_STYLE_PROPERTIES_EXCLUDE
from blenderbim.bim.ifc import IfcStore
from pathlib import Path
@@ -150,7 +150,7 @@ class CreateDrawing(bpy.types.Operator):
"Creates/refreshes a .svg drawing based on currently active camera.\n\n"
+ "SHIFT+CLICK to print all selected drawings"
)
print_all: bpy.props.BoolProperty(name="Print All", default=False)
print_all: bpy.props.BoolProperty(name="Print All", default=False, options={"SKIP_SAVE"})
@classmethod
def poll(cls, context):
@@ -158,12 +158,9 @@ class CreateDrawing(bpy.types.Operator):
def invoke(self, context, event):
# printing all drawings on shift+click
# make sure to use SKIP_SAVE on property, otherwise it might get stuck
if event.type == "LEFTMOUSE" and event.shift:
self.print_all = True
else:
# can't rely on default value since the line above
# will set the value to `True` for all future operator calls
self.print_all = False
return self.execute(context)
def execute(self, context):
@@ -177,7 +174,7 @@ class CreateDrawing(bpy.types.Operator):
for drawing_id in drawings_to_print:
if self.print_all:
bpy.ops.bim.activate_drawing(drawing=drawing_id)
bpy.ops.bim.activate_drawing(drawing=drawing_id, camera_view_point=False)
self.camera = context.scene.camera
self.camera_element = tool.Ifc.get_entity(self.camera)
@@ -189,7 +186,7 @@ class CreateDrawing(bpy.types.Operator):
self.cprops = self.camera.data.BIMCameraProperties
self.drawing_name = self.file.by_id(drawing_id).Name
self.metadata = tool.Drawing.get_drawing_metadata(self.camera_element)
self.get_scale()
self.get_scale(context)
if self.cprops.update_representation(self.camera):
bpy.ops.bim.update_representation(obj=self.camera.name, ifc_representation_class="")
@@ -220,7 +217,7 @@ class CreateDrawing(bpy.types.Operator):
open_with_user_command(context.preferences.addons["blenderbim"].preferences.svg_command, svg_path)
if self.print_all:
bpy.ops.bim.activate_drawing(drawing=original_drawing_id)
bpy.ops.bim.activate_drawing(drawing=original_drawing_id, camera_view_point=False)
return {"FINISHED"}
def get_camera_dimensions(self):
@@ -288,7 +285,7 @@ class CreateDrawing(bpy.types.Operator):
bpy.ops.render.render(write_still=True)
else:
previous_visibility = {}
for obj in self.camera.users_collection[0].objects:
for obj in self.camera.BIMObjectProperties.collection.objects:
if bpy.context.view_layer.objects.get(obj.name):
previous_visibility[obj.name] = obj.hide_get()
obj.hide_set(True)
@@ -481,11 +478,27 @@ class CreateDrawing(bpy.types.Operator):
tree.add_element(elem)
elements -= processed
if self.camera_element not in drawing_elements:
with profile("Camera element"):
# The camera must always be included, regardless of any include/exclude filters.
geom_settings = ifcopenshell.geom.settings(DISABLE_TRIANGULATION=True, STRICT_TOLERANCE=True)
it = ifcopenshell.geom.iterator(geom_settings, ifc, include=[self.camera_element])
for elem in self.yield_from_iterator(it):
self.serialiser.write(elem)
with profile("Finalizing"):
self.serialiser.finalize()
results = self.svg_buffer.get_value()
root = etree.fromstring(results)
group = root.find("{http://www.w3.org/2000/svg}g")
if not group:
with open(svg_path, "wb") as svg:
svg.write(etree.tostring(root))
return svg_path
self.move_projection_to_bottom(root)
self.merge_linework_and_add_metadata(root)
@@ -620,7 +633,6 @@ class CreateDrawing(bpy.types.Operator):
# Loop over the cell paths
for pi, p in enumerate(g2.getElementsByTagName("path")):
d = p.getAttribute("d")
coords = [co[1:].split(",") for co in d.split() if co[1:]]
polygon = shapely.Polygon(coords)
@@ -829,11 +841,15 @@ class CreateDrawing(bpy.types.Operator):
ifc = tool.Ifc.get()
for el in root.findall(".//{http://www.w3.org/2000/svg}g[@{http://www.ifcopenshell.org/ns}guid]"):
element = ifc.by_guid(el.get("{http://www.ifcopenshell.org/ns}guid"))
classes = self.get_svg_classes(element)
classes.append("cut")
el.set("class", " ".join(classes))
# Architectural convention only merges these objects. E.g. pipe segments and fittings shouldn't merge.
if not element.is_a("IfcWall") and not element.is_a("IfcSlab"):
continue
keys = []
for query in join_criteria:
key = ifcopenshell.util.selector.get_element_value(element, query)
@@ -865,7 +881,11 @@ class CreateDrawing(bpy.types.Operator):
if is_closed_polygon:
el.getparent().remove(el)
merged_polygons = shapely.ops.unary_union(polygons)
try:
merged_polygons = shapely.ops.unary_union(polygons)
except:
print("Warning. Portions of the merge failed. Please report a bug!", polygons)
merged_polygons = polygons
if type(merged_polygons) == shapely.MultiPolygon:
merged_polygons = merged_polygons.geoms
@@ -923,18 +943,14 @@ class CreateDrawing(bpy.types.Operator):
return svg_path
def get_scale(self):
if self.camera.data.BIMCameraProperties.diagram_scale == "CUSTOM":
self.human_scale, fraction = self.camera.data.BIMCameraProperties.custom_diagram_scale.split("|")
else:
self.human_scale, fraction = self.camera.data.BIMCameraProperties.diagram_scale.split("|")
def get_scale(self, context):
diagram_scale = tool.Drawing.get_diagram_scale(self.camera)
self.human_scale = diagram_scale["HumanScale"]
self.scale = tool.Drawing.get_scale_ratio(diagram_scale["Scale"])
if self.camera.data.BIMCameraProperties.is_nts:
if ifcopenshell.util.element.get_pset(self.camera_element, "EPset_Drawing", "IsNTS"):
self.human_scale = "NTS"
numerator, denominator = fraction.split("/")
self.scale = float(numerator) / float(denominator)
def is_landscape(self, render):
return render.resolution_x > render.resolution_y
@@ -1004,12 +1020,14 @@ class AddSheet(bpy.types.Operator, Operator):
class OpenSheet(bpy.types.Operator, Operator):
bl_idname = "bim.open_sheet"
bl_label = "Open Sheet"
bl_label = "Open Sheet Layout"
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
self.props = context.scene.DocProperties
sheet = tool.Ifc.get().by_id(self.props.sheets[self.props.active_sheet_index].ifc_definition_id)
sheet_builder = sheeter.SheetBuilder()
sheet_builder.update_sheet_drawing_sizes(sheet)
core.open_sheet(tool.Drawing, sheet=sheet)
@@ -1026,7 +1044,7 @@ class AddDrawingToSheet(bpy.types.Operator, Operator):
def _execute(self, context):
props = context.scene.DocProperties
active_drawing = props.drawings[props.active_drawing_index]
active_sheet = props.sheets[props.active_sheet_index]
active_sheet = tool.Drawing.get_active_sheet(context)
drawing = tool.Ifc.get().by_id(active_drawing.ifc_definition_id)
drawing_reference = tool.Drawing.get_drawing_document(drawing)
@@ -1085,6 +1103,7 @@ class RemoveDrawingFromSheet(bpy.types.Operator, Operator):
class CreateSheets(bpy.types.Operator, Operator):
bl_idname = "bim.create_sheets"
bl_label = "Create Sheets"
bl_description = "Build a sheet from the sheet layout"
bl_options = {"REGISTER", "UNDO"}
@classmethod
@@ -1121,7 +1140,7 @@ class CreateSheets(bpy.types.Operator, Operator):
has_sheet_reference = True
elif reference.Description == "RASTER":
if reference.Location in raster_references:
del raster_references[reference.Location]
raster_references.remove(reference.Location)
else:
tool.Ifc.run("document.remove_reference", reference=reference)
@@ -1169,16 +1188,13 @@ class SelectAllDrawings(bpy.types.Operator):
bl_label = "Select All Drawings"
view: bpy.props.StringProperty()
bl_description = "Select all drawings in the drawing list.\n\n" + "SHIFT+CLICK to deselect all drawings"
select_all: bpy.props.BoolProperty(name="Open All", default=False)
select_all: bpy.props.BoolProperty(name="Open All", default=False, options={"SKIP_SAVE"})
def invoke(self, context, event):
# deselect all drawings on shift+click
# make sure to use SKIP_SAVE on property, otherwise it might get stuck
if event.type == "LEFTMOUSE" and event.shift:
self.select_all = False
else:
# can't rely on default value since the line above
# will set the value to `True` for all future operator calls
self.select_all = True
return self.execute(context)
def execute(self, context):
@@ -1197,16 +1213,13 @@ class OpenDrawing(bpy.types.Operator):
+ 'or using "svg_command" from the BlenderBIM preferences (if provided).\n\n'
+ "SHIFT+CLICK to open all selected drawings"
)
open_all: bpy.props.BoolProperty(name="Open All", default=False)
open_all: bpy.props.BoolProperty(name="Open All", default=False, options={"SKIP_SAVE"})
def invoke(self, context, event):
# opening all drawings on shift+click
# make sure to use SKIP_SAVE on property, otherwise it might get stuck
if event.type == "LEFTMOUSE" and event.shift:
self.open_all = True
else:
# can't rely on default value since the line above
# will set the value to `True` for all future operator calls
self.open_all = False
return self.execute(context)
def execute(self, context):
@@ -1245,9 +1258,13 @@ class ActivateModel(bpy.types.Operator):
bl_description = "Activates the model view"
def execute(self, context):
dprops = bpy.context.scene.DocProperties
dprops.active_drawing_id = 0
CutDecorator.uninstall()
bpy.ops.object.hide_view_clear()
if not bpy.app.background:
bpy.ops.object.hide_view_clear()
subcontext = ifcopenshell.util.representation.get_context(tool.Ifc.get(), "Model", "Body", "MODEL_VIEW")
@@ -1280,17 +1297,40 @@ class ActivateDrawing(bpy.types.Operator):
bl_idname = "bim.activate_drawing"
bl_label = "Activate Drawing"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Activates the selected drawing view"
bl_description = "Activates the selected drawing view.\n\n" + "ALT+CLICK to keep the viewport position"
drawing: bpy.props.IntProperty()
camera_view_point: bpy.props.BoolProperty(name="Camera View Point", default=True, options={"SKIP_SAVE"})
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.camera_view_point = False
return self.execute(context)
def execute(self, context):
drawing = tool.Ifc.get().by_id(self.drawing)
dprops = bpy.context.scene.DocProperties
if not self.camera_view_point:
viewport_position = tool.Blender.get_viewport_position()
core.activate_drawing_view(tool.Ifc, tool.Drawing, drawing=drawing)
bpy.context.scene.DocProperties.active_drawing_id = self.drawing
if not self.camera_view_point:
tool.Blender.set_viewport_position(viewport_position)
dprops.active_drawing_id = self.drawing
# reset DrawingsData to reload_drawing_styles work correctly
DrawingsData.is_loaded = False
dprops.drawing_styles.clear()
if ifcopenshell.util.element.get_pset(drawing, "EPset_Drawing", "HasUnderlay"):
bpy.ops.bim.reload_drawing_styles()
bpy.ops.bim.activate_drawing_style()
core.sync_references(tool.Ifc, tool.Collector, tool.Drawing, drawing=tool.Ifc.get().by_id(self.drawing))
CutDecorator.install(context)
tool.Drawing.show_decorations()
return {"FINISHED"}
@@ -1318,7 +1358,7 @@ class ResizeText(bpy.types.Operator):
# TODO: check undo redo
def execute(self, context):
for obj in context.scene.camera.users_collection[0].objects:
for obj in context.scene.camera.BIMObjectProperties.collection.objects:
if isinstance(obj.data, bpy.types.TextCurve):
annotation.Annotator.resize_text(obj)
return {"FINISHED"}
@@ -1331,16 +1371,13 @@ class RemoveDrawing(bpy.types.Operator, Operator):
bl_description = "Remove currently selected drawing.\n\n" + "SHIFT+CLICK to remove all selected drawings"
drawing: bpy.props.IntProperty()
remove_all: bpy.props.BoolProperty(name="Remove All", default=False)
remove_all: bpy.props.BoolProperty(name="Remove All", default=False, options={"SKIP_SAVE"})
def invoke(self, context, event):
# removing all selected drawings on shift+click
# make sure to use SKIP_SAVE on property, otherwise it might get stuck
if event.type == "LEFTMOUSE" and event.shift:
self.remove_all = True
else:
# can't rely on default value since the line above
# will set the value to `True` for all future operator calls
self.remove_all = False
return self.execute(context)
def _execute(self, context):
@@ -1351,24 +1388,88 @@ class RemoveDrawing(bpy.types.Operator, Operator):
else:
drawings = [tool.Ifc.get().by_id(self.drawing)]
print("Removing drawings: {}".format([d for d in drawings]))
removed_drawings = [drawing.id() for drawing in drawings]
for drawing in drawings:
core.remove_drawing(tool.Ifc, tool.Drawing, drawing=drawing)
class AddDrawingStyle(bpy.types.Operator):
class ReloadDrawingStyles(bpy.types.Operator):
bl_idname = "bim.reload_drawing_styles"
bl_label = "Reload Drawing Styles"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Reload drawing styles for the active camera"
def execute(self, context):
if not DrawingsData.is_loaded:
DrawingsData.load()
drawing_pset_data = DrawingsData.data["active_drawing_pset_data"]
camera_props = context.active_object.data.BIMCameraProperties
# added this part as a temporary fallback
# TODO: should remove it a bit later when projects get more accommodated
# with saving shadingstyles to ifc and separate json file
if "ShadingStyles" not in drawing_pset_data:
ifc_file = tool.Ifc.get()
pset = ifc_file.by_id(drawing_pset_data["id"])
edit_properties = {
"ShadingStyles": (
shading_styles_path := tool.Drawing.get_default_drawing_resource_path("ShadingStyles")
),
"CurrentShadingStyle": tool.Drawing.get_default_shading_style(),
}
ifcopenshell.api.run("pset.edit_pset", ifc_file, pset=pset, properties=edit_properties)
tool.Drawing.setup_shading_styles_path(shading_styles_path)
DrawingsData.load()
drawing_pset_data = DrawingsData.data["active_drawing_pset_data"]
if "ShadingStyles" not in drawing_pset_data:
self.report({"ERROR"}, "Could not find shading styles path in EPset_Drawing.ShadingStyles.")
return {"CANCELLED"}
rel_path = drawing_pset_data["ShadingStyles"]
current_style = drawing_pset_data.get("CurrentShadingStyle", None)
json_path = Path(tool.Ifc.resolve_uri(rel_path))
if not json_path.exists():
self.report({"ERROR"}, "Shading styles file not found: {}".format(json_path))
return {"CANCELLED"}
with open(json_path, "r") as fi:
shading_styles_json = json.load(fi)
drawing_styles = context.scene.DocProperties.drawing_styles
drawing_styles.clear()
styles = [style for style in shading_styles_json]
for style_name in styles:
style_data = shading_styles_json[style_name]
drawing_style = drawing_styles.add()
drawing_style["name"] = style_name # setting as attribute to avoid triggering setter
drawing_style.render_type = style_data["render_type"]
drawing_style.raster_style = json.dumps(style_data["raster_style"])
if current_style is not None:
camera_props.active_drawing_style_index = styles.index(current_style)
return {"FINISHED"}
class AddDrawingStyle(bpy.types.Operator, Operator):
bl_idname = "bim.add_drawing_style"
bl_label = "Add Drawing Style"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
new = context.scene.DocProperties.drawing_styles.add()
new.name = "New Drawing Style"
drawing_styles = context.scene.DocProperties.drawing_styles
new = drawing_styles.add()
# drawing style is saved to ifc on rename
new.name = tool.Blender.ensure_unique_name("New Drawing Style", drawing_styles)
context.scene.camera.data.BIMCameraProperties.active_drawing_style_index = len(drawing_styles) - 1
return {"FINISHED"}
class RemoveDrawingStyle(bpy.types.Operator):
class RemoveDrawingStyle(bpy.types.Operator, Operator):
bl_idname = "bim.remove_drawing_style"
bl_label = "Remove Drawing Style"
bl_options = {"REGISTER", "UNDO"}
@@ -1376,13 +1477,17 @@ class RemoveDrawingStyle(bpy.types.Operator):
def execute(self, context):
context.scene.DocProperties.drawing_styles.remove(self.index)
context.scene.camera.data.BIMCameraProperties.active_drawing_style_index = max(self.index - 1, 0)
bpy.ops.bim.save_drawing_styles_data()
return {"FINISHED"}
class SaveDrawingStyle(bpy.types.Operator):
class SaveDrawingStyle(bpy.types.Operator, Operator):
bl_idname = "bim.save_drawing_style"
bl_label = "Save Drawing Style"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Save current render setting to currently selected drawing style. Also resaves styles data to IFC"
index: bpy.props.StringProperty()
# TODO: check undo redo
@@ -1390,19 +1495,46 @@ class SaveDrawingStyle(bpy.types.Operator):
space = self.get_view_3d(context) # Do not remove. It is used later in eval
scene = context.scene
style = {}
for prop in RasterStyleProperty:
value = eval(prop.value)
eval_namespace = {"context": context, "scene": scene, "space": space}
def add_prop_to_style(prop_path, context, scene, space):
value = eval(prop_path)
if not isinstance(value, str):
try:
value = tuple(value)
except TypeError:
pass
style[prop.value] = value
style[prop_path] = value
for prop in RasterStyleProperty:
if prop.name.startswith("EVAL_PROP"):
prop_path = prop.value
add_prop_to_style(prop_path, **eval_namespace)
else:
props_source_path = prop.value
props_source = eval(props_source_path)
for prop_name in dir(props_source):
if prop_name.startswith("__"):
continue
prop_path = f"{props_source_path}.{prop_name}"
prop_value = eval(prop_path)
if (
not isinstance(prop_value, (int, float, bool, str, Color, Vector))
or props_source.is_property_readonly(prop_name)
or prop_path in RASTER_STYLE_PROPERTIES_EXCLUDE
):
continue
add_prop_to_style(prop_path, **eval_namespace)
if self.index:
index = int(self.index)
else:
index = context.active_object.data.BIMCameraProperties.active_drawing_style_index
scene.DocProperties.drawing_styles[index].raster_style = json.dumps(style)
bpy.ops.bim.save_drawing_styles_data()
return {"FINISHED"}
def get_view_3d(self, context):
@@ -1415,19 +1547,84 @@ class SaveDrawingStyle(bpy.types.Operator):
return space
class ActivateDrawingStyle(bpy.types.Operator):
class SaveDrawingStylesData(bpy.types.Operator, Operator):
bl_idname = "bim.save_drawing_styles_data"
bl_label = "Save Drawing Styles Data"
bl_options = {"REGISTER", "UNDO"}
skip_updating_current_style: bpy.props.BoolProperty(default=False)
rename_style: bpy.props.BoolProperty(default=False)
rename_style_from: bpy.props.StringProperty(default="")
rename_style_to: bpy.props.StringProperty(default="")
def execute(self, context):
if not DrawingsData.is_loaded:
DrawingsData.load()
drawing_pset_data = DrawingsData.data["active_drawing_pset_data"]
drawing_styles = context.scene.DocProperties.drawing_styles
rel_path = drawing_pset_data["ShadingStyles"]
current_style = drawing_pset_data.get("CurrentShadingStyle", None)
json_path = Path(tool.Ifc.resolve_uri(rel_path))
if not json_path.exists():
self.report({"ERROR"}, "Shading styles file not found: {}".format(json_path))
return {"CANCELLED"}
styles_data = {}
for style in drawing_styles:
style_data = {"render_type": style.render_type, "raster_style": json.loads(style.raster_style)}
styles_data[style.name] = style_data
with open(json_path, "w") as fo:
json.dump(styles_data, fo, indent=4)
# TODO: currently it doesn't update current style for the other drawings
# handling case when current style is not present in styles saved in ifc
if not self.skip_updating_current_style and current_style not in styles_data and current_style is not None:
# style was renamed
if self.rename_style and current_style == self.rename_style_from:
new_style_name = self.rename_style_to
# style was removed
else:
new_style_name = None
ifc_file = tool.Ifc.get()
drawing = ifc_file.by_id(context.scene.DocProperties.active_drawing_id)
pset = tool.Pset.get_element_pset(drawing, "EPset_Drawing")
ifcopenshell.api.run(
"pset.edit_pset", ifc_file, pset=pset, properties={"CurrentShadingStyle": new_style_name}
)
blenderbim.bim.handler.refresh_ui_data()
return {"FINISHED"}
class ActivateDrawingStyle(bpy.types.Operator, Operator):
bl_idname = "bim.activate_drawing_style"
bl_label = "Activate Drawing Style"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
scene = context.scene
if scene.camera.data.BIMCameraProperties.active_drawing_style_index < len(scene.DocProperties.drawing_styles):
self.drawing_style = scene.DocProperties.drawing_styles[
scene.camera.data.BIMCameraProperties.active_drawing_style_index
]
self.set_raster_style(context)
self.set_query(context)
ifc_file = tool.Ifc.get()
active_drawing_style_index = scene.camera.data.BIMCameraProperties.active_drawing_style_index
if active_drawing_style_index >= len(scene.DocProperties.drawing_styles):
self.report({"ERROR"}, "Could not find active drawing style")
return {"CANCELLED"}
self.drawing_style = scene.DocProperties.drawing_styles[active_drawing_style_index]
self.set_raster_style(context)
self.set_query(context)
drawing = ifc_file.by_id(scene.DocProperties.active_drawing_id)
pset = tool.Pset.get_element_pset(drawing, "EPset_Drawing")
ifcopenshell.api.run(
"pset.edit_pset", ifc_file, pset=pset, properties={"CurrentShadingStyle": self.drawing_style.name}
)
blenderbim.bim.handler.refresh_ui_data()
return {"FINISHED"}
def set_raster_style(self, context):
@@ -1512,10 +1709,14 @@ class AddSchedule(bpy.types.Operator, Operator):
ifc_path = tool.Ifc.get_path()
if os.path.isfile(ifc_path):
ifc_path = os.path.dirname(ifc_path)
filepath = os.path.relpath(filepath, ifc_path)
core.add_schedule(
# taking into account different drives on windows
if Path(filepath).drive == Path(ifc_path).drive:
filepath = os.path.relpath(filepath, ifc_path)
core.add_document(
tool.Ifc,
tool.Drawing,
"SCHEDULE",
uri=filepath,
)
@@ -1531,7 +1732,7 @@ class RemoveSchedule(bpy.types.Operator, Operator):
schedule: bpy.props.IntProperty()
def _execute(self, context):
core.remove_schedule(tool.Ifc, tool.Drawing, schedule=tool.Ifc.get().by_id(self.schedule))
core.remove_document(tool.Ifc, tool.Drawing, "SCHEDULE", schedule=tool.Ifc.get().by_id(self.schedule))
class OpenSchedule(bpy.types.Operator, Operator):
@@ -1566,7 +1767,7 @@ class AddScheduleToSheet(bpy.types.Operator, Operator):
def _execute(self, context):
props = context.scene.DocProperties
active_schedule = props.schedules[props.active_schedule_index]
active_sheet = props.sheets[props.active_sheet_index]
active_sheet = tool.Drawing.get_active_sheet(context)
schedule = tool.Ifc.get().by_id(active_schedule.ifc_definition_id)
if tool.Ifc.get_schema() == "IFC2X3":
schedule_location = tool.Drawing.get_path_with_ext(schedule.DocumentReferences[0].Location, "svg")
@@ -1602,11 +1803,116 @@ class AddScheduleToSheet(bpy.types.Operator, Operator):
sheet_builder = sheeter.SheetBuilder()
sheet_builder.data_dir = context.scene.BIMProperties.data_dir
sheet_builder.add_schedule(reference, schedule, sheet)
sheet_builder.add_document(reference, schedule, sheet)
tool.Drawing.import_sheets()
class AddReferenceToSheet(bpy.types.Operator, Operator):
bl_idname = "bim.add_reference_to_sheet"
bl_label = "Add Reference To Sheet"
bl_options = {"REGISTER", "UNDO"}
@classmethod
def poll(cls, context):
props = context.scene.DocProperties
return props.references and props.sheets and context.scene.BIMProperties.data_dir
def _execute(self, context):
props = context.scene.DocProperties
active_reference = props.references[props.active_reference_index]
active_sheet = tool.Drawing.get_active_sheet(context)
reference = tool.Ifc.get().by_id(active_reference.ifc_definition_id)
if tool.Ifc.get_schema() == "IFC2X3":
reference_location = tool.Drawing.get_path_with_ext(reference.DocumentReferences[0].Location, "svg")
else:
reference_location = tool.Drawing.get_path_with_ext(reference.HasDocumentReferences[0].Location, "svg")
sheet = tool.Ifc.get().by_id(active_sheet.ifc_definition_id)
if not sheet.is_a("IfcDocumentInformation"):
return
references = tool.Drawing.get_document_references(sheet)
has_reference = False
for reference in references:
if reference.Location == reference_location:
has_reference = True
break
if has_reference:
return
if not tool.Drawing.does_file_exist(tool.Ifc.resolve_uri(reference_location)):
self.report({"ERROR"}, f"Cannot find reference svg by path {reference_location}.")
return
reference = tool.Ifc.run("document.add_reference", information=sheet)
id_attr = "ItemReference" if tool.Ifc.get_schema() == "IFC2X3" else "Identification"
attributes = {
id_attr: str(len([r for r in references if r.Description in ("DRAWING", "REFERENCE")]) + 1),
"Location": reference_location,
"Description": "REFERENCE",
}
tool.Ifc.run("document.edit_reference", reference=reference, attributes=attributes)
sheet_builder = sheeter.SheetBuilder()
sheet_builder.data_dir = context.scene.BIMProperties.data_dir
sheet_builder.add_document(reference, reference, sheet)
tool.Drawing.import_sheets()
class AddReference(bpy.types.Operator, Operator):
bl_idname = "bim.add_reference"
bl_label = "Add Reference"
bl_options = {"REGISTER", "UNDO"}
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
filter_glob: bpy.props.StringProperty(default="*.svg", options={"HIDDEN"})
use_relative_path: bpy.props.BoolProperty(name="Use Relative Path", default=True)
def _execute(self, context):
filepath = self.filepath
if self.use_relative_path:
ifc_path = tool.Ifc.get_path()
if os.path.isfile(ifc_path):
ifc_path = os.path.dirname(ifc_path)
# taking into account different drives on windows
if Path(filepath).drive == Path(ifc_path).drive:
filepath = os.path.relpath(filepath, ifc_path)
core.add_document(
tool.Ifc,
tool.Drawing,
"REFERENCE",
uri=filepath,
)
def invoke(self, context, event):
context.window_manager.fileselect_add(self)
return {"RUNNING_MODAL"}
class RemoveReference(bpy.types.Operator, Operator):
bl_idname = "bim.remove_reference"
bl_label = "Remove Reference"
bl_options = {"REGISTER", "UNDO"}
reference: bpy.props.IntProperty()
def _execute(self, context):
core.remove_document(tool.Ifc, tool.Drawing, "REFERENCE", document=tool.Ifc.get().by_id(self.reference))
class OpenReference(bpy.types.Operator, Operator):
bl_idname = "bim.open_reference"
bl_label = "Open Reference"
bl_options = {"REGISTER", "UNDO"}
reference: bpy.props.IntProperty()
def _execute(self, context):
core.open_reference(tool.Drawing, reference=tool.Ifc.get().by_id(self.reference))
# TODO: dead code - drawing style attributes are never used?
class AddDrawingStyleAttribute(bpy.types.Operator):
bl_idname = "bim.add_drawing_style_attribute"
bl_label = "Add Drawing Style Attribute"
@@ -1618,6 +1924,7 @@ class AddDrawingStyleAttribute(bpy.types.Operator):
return {"FINISHED"}
# TODO: dead code - drawing style attributes are never used?
class RemoveDrawingStyleAttribute(bpy.types.Operator):
bl_idname = "bim.remove_drawing_style_attribute"
bl_label = "Remove Drawing Style Attribute"
@@ -1930,6 +2237,24 @@ class DisableEditingSchedules(bpy.types.Operator, Operator):
core.disable_editing_schedules(tool.Drawing)
class LoadReferences(bpy.types.Operator, Operator):
bl_idname = "bim.load_references"
bl_label = "Load References"
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
core.load_references(tool.Drawing)
class DisableEditingReferences(bpy.types.Operator, Operator):
bl_idname = "bim.disable_editing_references"
bl_label = "Disable Editing References"
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
core.disable_editing_references(tool.Drawing)
class LoadDrawings(bpy.types.Operator, Operator):
bl_idname = "bim.load_drawings"
bl_label = "Load Drawings"
@@ -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):
@@ -156,13 +169,32 @@ def update_drawing_name(self, context):
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 +231,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)
@@ -220,9 +255,9 @@ class Drawing(PropertyGroup):
is_selected: BoolProperty(name="Is Selected", 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 +271,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 +288,20 @@ 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"
RASTER_STYLE_PROPERTIES_EXCLUDE = (
"scene.render.filepath",
)
class DocProperties(PropertyGroup):
@@ -286,6 +311,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 +329,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 +340,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 +352,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 +367,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"
@@ -471,6 +501,7 @@ ANNOTATION_TYPES_DATA = {
"BREAKLINE": ("Breakline", "", "FCURVE", "mesh"),
"LINEWORK": ("Line", "", "MESH_MONKEY", "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"),
}
@@ -494,6 +525,10 @@ 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
@@ -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,61 @@ 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
# replace urls url(#marker) with url(#prefix-marker)
# since url(#marker.prefix) doesn't seem to work
text = re.sub(r"url\(#([^\)]+)\)", rf"url(#{prefix}-\1)", text)
style.text = text
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 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 +355,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))
@@ -225,17 +246,24 @@ class SvgWriter:
)
)
# TODO: allow metric to be configurable
rl = (matrix_world @ points[0].co.xyz).z
rl_value = (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)
rl = helper.format_distance(rl_value, precision=self.precision, decimal_places=self.decimal_places)
else:
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
rl /= unit_scale
rl = rl_value / unit_scale
rl = ifcopenshell.util.geolocation.auto_z2e(tool.Ifc.get(), rl)
rl *= unit_scale
rl = "{:.3f}m".format(rl)
text_style = self.get_box_alignment_parameters("bottom-left")
self.svg.add(self.svg.text(f"RL +{rl}", insert=tuple(text_position), class_="SECTIONLEVEL", **text_style))
text_style = SvgWriter.get_box_alignment_parameters("bottom-left")
self.svg.add(
self.svg.text(
"RL {}{}".format("" if rl_value < 0 else "+", rl),
insert=tuple(text_position),
class_="SECTIONLEVEL",
**text_style,
)
)
if tag:
self.svg.add(self.svg.text(tag, insert=(text_position[0], text_position[1] - 5), **text_style))
@@ -263,7 +291,7 @@ class SvgWriter:
"UP",
insert=tuple(text_position),
class_="STAIR",
**self.get_box_alignment_parameters("center"),
**SvgWriter.get_box_alignment_parameters("center"),
)
)
@@ -287,7 +315,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,
@@ -396,8 +424,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 +490,94 @@ 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):
vector = direction * size
shrinked_vector = size // segment_width * segment_width * direction
scale = [1 if vector[i] == 0 else vector[i] / shrinked_vector[i] for i in range(2)]
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 = int(pattern_length // segment_width)
pattern_dir_step = pattern_dir * segment_width
points = [pattern_dir_step * i for i in range(segments)]
polyline_style = f"marker: url(#{marker_id}); stroke: 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 +618,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 +677,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 +710,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
@@ -617,7 +737,8 @@ class SvgWriter:
return (reference_id, sheet_id)
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 +785,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 +795,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 +814,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,8 +824,9 @@ 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
@@ -707,32 +835,39 @@ class SvgWriter:
text = tool.Drawing.replace_text_literal_variables(text_literal.Literal, product)
attribs = {
"transform": transform,
"transform": text_transform,
"style": "font-size: 0;",
}
def add_text_tag(add_fill_bg):
nonlocal line_number
text_tag = self.svg.text(
"",
**(attribs | {"filter": "url(#fill-background)"}) if add_fill_bg else attribs,
**self.get_box_alignment_parameters(text_literal.BoxAlignment),
**SvgWriter.get_box_alignment_parameters(text_literal.BoxAlignment),
)
self.svg.add(text_tag)
text_lines = text.replace("\\n", "\n").split("\n")
for line_number, text_line in enumerate(text_lines):
for text_line in 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)
# note that tspan doesn't support using `transform` attribute
# so we use (0,0) position because tspan is already offseted by text transform
tspan = self.svg.tspan(text_line, class_=classes_str, insert=(0, 0))
# doing it here and not in tspan constructor because constructor adds unnecessary spaces
tspan.update({"dy": f"{line_number}em"})
text_tag.add(tspan)
line_number += 1
if add_fill_bg:
# return line_number back to the original value
line_number -= len(text_lines)
if "fill-bg" in classes:
add_text_tag(True)
add_text_tag(False)
def draw_break_annotations(self, obj):
x_offset = self.raw_width / 2
y_offset = self.raw_height / 2
@@ -775,25 +910,25 @@ class SvgWriter:
text_position = Vector(
(
(x_offset + projected_points[0].x) * self.svg_scale,
((y_offset - projected_points[0].y) * self.svg_scale) - 2.5,
((y_offset - projected_points[0].y) * self.svg_scale) - 1.0,
)
)
# TODO: allow metric to be configurable
rl = (matrix_world @ points[0].co).z
rl_value = (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)
rl = helper.format_distance(rl_value, precision=self.precision, decimal_places=self.decimal_places)
else:
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
rl /= unit_scale
rl = rl_value / unit_scale
rl = ifcopenshell.util.geolocation.auto_z2e(tool.Ifc.get(), rl)
rl *= unit_scale
rl = "{:.3f}m".format(rl)
box_alignment = "bottom-left" if projected_points[0].x <= projected_points[-1].x else "bottom-right"
text_style = self.get_box_alignment_parameters(box_alignment)
text_style = SvgWriter.get_box_alignment_parameters(box_alignment)
self.svg.add(
self.svg.text(
"RL +{}".format(rl),
"{}{}".format("" if rl_value < 0 else "+", rl),
insert=tuple(text_position),
class_="PLANLEVEL",
**text_style,
@@ -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
@@ -973,7 +1108,7 @@ class SvgWriter:
)
text_position += text_offset
text_style = self.get_box_alignment_parameters("center")
text_style = SvgWriter.get_box_alignment_parameters("center")
radius = (points[-1].co - points[-2].co).length
radius = helper.format_distance(radius, precision=self.precision, decimal_places=self.decimal_places)
tag = element.Description or f"R{radius}"
@@ -1046,7 +1181,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 +1206,8 @@ 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"],
)
def draw_dimension_annotations(self, obj):
@@ -1092,6 +1229,8 @@ 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"],
)
def draw_measureit_arch_dimension_annotations(self):
@@ -1115,6 +1254,8 @@ class SvgWriter:
text_format=lambda x: x,
show_description_only=False,
suppress_zero_inches=False,
text_prefix="",
text_suffix="",
):
offset = Vector([self.raw_width, self.raw_height]) / 2
v0 = self.project_point_onto_camera(v0_global)
@@ -1135,27 +1276,86 @@ class SvgWriter:
# 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)}
if not show_description_only:
text = f"{text_prefix}{str(dimension)}{text_suffix}"
text_tag = self.create_text_tag(
text,
text_position + perpendicular,
angle,
"bottom-middle",
"DIMENSION",
text_format=text_format,
multiline=True,
multiline_to_bottom=False,
)
self.svg.add(text_tag)
if dimension_text:
text_tag = self.create_text_tag(
dimension_text,
text_position - perpendicular,
angle,
"top-middle",
"DIMENSION",
text_format=text_format,
multiline=True,
multiline_to_bottom=True,
)
self.svg.add(text_tag)
elif show_description_only and dimension_text:
text_tag = self.create_text_tag(
dimension_text,
text_position + perpendicular,
angle,
"bottom-middle",
"DIMENSION",
text_format=text_format,
multiline=True,
multiline_to_bottom=False,
)
self.svg.add(text_tag)
def create_text_tag(
self,
text,
text_position,
angle,
box_alignment,
class_str,
text_format=lambda x: x,
multiline=False,
multiline_to_bottom=False,
):
if not multiline:
text_kwargs = {"transform": "rotate({} {} {})".format(angle, 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)),
class_=class_str,
**(text_kwargs | SvgWriter.get_box_alignment_parameters(box_alignment)),
)
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"))
text_position_svg_str = ", ".join(map(str, text_position))
text_transform = f"translate({text_position_svg_str}) rotate({angle})"
text_kwargs = {
"transform": text_transform,
"style": "font-size: 0;",
}
elif show_description_only and dimension_text:
self.svg.add(create_text_tag(dimension_text, text_position + perpendicular, "bottom-middle"))
text_tag = self.svg.text("", **text_kwargs, **SvgWriter.get_box_alignment_parameters(box_alignment))
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):
tspan = self.svg.tspan(text_format(text_line), class_=class_str, insert=(0, 0))
tspan.update({"dy": f"{line_number if multiline_to_bottom else -line_number}em"})
text_tag.add(tspan)
return text_tag
def project_point_onto_camera(self, point):
# TODO is this needlessly complex?
@@ -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,38 +109,53 @@ 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):
@@ -161,11 +174,7 @@ class BIM_PT_drawings(Panel):
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
@@ -229,22 +238,18 @@ class BIM_PT_schedules(Panel):
return 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,6 +273,58 @@ class BIM_PT_schedules(Panel):
)
def draw_project_not_saved_ui(self):
row = self.layout.row()
row.label(text="Project Not Yet Saved", icon="ERROR")
row = self.layout.row()
op = row.operator("export_ifc.bim", icon="EXPORT", text="Save Project")
op.should_save_as = False
class BIM_PT_references(Panel):
bl_label = "References"
bl_idname = "BIM_PT_references"
bl_space_type = "VIEW_3D"
bl_region_type = "UI"
bl_category = "BIM Documentation"
@classmethod
def poll(cls, context):
return 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"
@@ -284,11 +341,7 @@ class BIM_PT_sheets(Panel):
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 +365,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
@@ -450,55 +504,55 @@ class BIM_PT_annotation_utilities(Panel):
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.data_type = tool.Drawing.get_annotation_data_type(op.object_type)
op = row.operator("bim.add_annotation", text="Angle", icon="DRIVER_ROTATIONAL_DIFFERENCE")
op.object_type = "ANGLE"
op.data_type = "curve"
op.data_type = tool.Drawing.get_annotation_data_type(op.object_type)
row = layout.row(align=True)
op = row.operator("bim.add_annotation", text="Radius", icon="FORWARD")
op.object_type = "RADIUS"
op.data_type = "curve"
op.data_type = tool.Drawing.get_annotation_data_type(op.object_type)
op = row.operator("bim.add_annotation", text="Diameter", icon="ARROW_LEFTRIGHT")
op.object_type = "DIAMETER"
op.data_type = "curve"
op.data_type = tool.Drawing.get_annotation_data_type(op.object_type)
row = layout.row(align=True)
op = row.operator("bim.add_annotation", text="Text", icon="SMALL_CAPS")
op.object_type = "TEXT"
op.data_type = "empty"
op.data_type = tool.Drawing.get_annotation_data_type(op.object_type)
op = row.operator("bim.add_annotation", text="Leader", icon="TRACKING_BACKWARDS")
op.object_type = "TEXT_LEADER"
op.data_type = "curve"
op.data_type = tool.Drawing.get_annotation_data_type(op.object_type)
row = layout.row(align=True)
op = row.operator("bim.add_annotation", text="Stair Arrow", icon="SCREEN_BACK")
op.object_type = "STAIR_ARROW"
op.data_type = "curve"
op.data_type = tool.Drawing.get_annotation_data_type(op.object_type)
op = row.operator("bim.add_annotation", text="Hidden", icon="CON_TRACKTO")
op.object_type = "HIDDEN_LINE"
op.data_type = "mesh"
op.data_type = tool.Drawing.get_annotation_data_type(op.object_type)
row = layout.row(align=True)
op = row.operator("bim.add_annotation", text="Level (Plan)", icon="SORTBYEXT")
op.object_type = "PLAN_LEVEL"
op.data_type = "curve"
op.data_type = tool.Drawing.get_annotation_data_type(op.object_type)
op = row.operator("bim.add_annotation", text="Level (Section)", icon="TRIA_DOWN")
op.object_type = "SECTION_LEVEL"
op.data_type = "curve"
op.data_type = tool.Drawing.get_annotation_data_type(op.object_type)
row = layout.row(align=True)
op = row.operator("bim.add_annotation", text="Breakline", icon="FCURVE")
op.object_type = "BREAKLINE"
op.data_type = "mesh"
op.data_type = tool.Drawing.get_annotation_data_type(op.object_type)
op = row.operator("bim.add_annotation", text="Line", icon="MESH_MONKEY")
op.object_type = "LINEWORK"
op.data_type = "mesh"
op.data_type = tool.Drawing.get_annotation_data_type(op.object_type)
row = layout.row(align=True)
op = row.operator("bim.add_annotation", text="Batting", icon="FORCE_FORCE")
op.object_type = "BATTING"
op.data_type = "mesh"
op.data_type = tool.Drawing.get_annotation_data_type(op.object_type)
op.description = "Add batting annotation.\nThickness could be changed through Thickness property of BBIM_Batting property set"
op = row.operator("bim.add_annotation", text="Fill Area", icon="NODE_TEXTURE")
op.object_type = "FILL_AREA"
@@ -506,7 +560,10 @@ class BIM_PT_annotation_utilities(Panel):
row = layout.row(align=True)
op = row.operator("bim.add_annotation", text="Fall", icon="SORT_ASC")
op.object_type = "FALL"
op.data_type = "curve"
op.data_type = tool.Drawing.get_annotation_data_type(op.object_type)
op = row.operator("bim.add_annotation", text="Revision", icon="VOLUME_DATA")
op.object_type = "REVISION_CLOUD"
op.data_type = tool.Drawing.get_annotation_data_type(op.object_type)
row = layout.row(align=True)
row.prop(self.props, "should_draw_decorations", text="Viewport Annotations")
@@ -561,6 +618,8 @@ 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'}"
@@ -47,7 +47,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")]}),
@@ -108,7 +107,7 @@ def create_annotation_type(context):
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))
ifc_context = tool.Drawing.get_annotation_context(tool.Drawing.get_drawing_target_view(drawing), object_type)
element = tool.Drawing.run_root_assign_class(
obj=obj,
@@ -133,7 +132,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,
@@ -179,8 +178,7 @@ 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):
@@ -194,14 +192,6 @@ class AnnotationToolUI:
if DecoratorData.get_ifc_text_data(bpy.context.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
@@ -63,6 +63,8 @@ def register():
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")
@@ -90,6 +92,8 @@ def register():
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
@@ -199,6 +199,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 +373,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 +381,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,21 +390,55 @@ 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):
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"
@@ -519,6 +561,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 +571,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()
@@ -720,17 +766,26 @@ class OverrideModeSetEdit(bpy.types.Operator):
if context.active_object:
context.active_object.select_set(True)
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 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.
@@ -741,6 +796,36 @@ class OverrideModeSetEdit(bpy.types.Operator):
if not representation:
continue
if (
tool.Pset.get_element_pset(element, "BBIM_Door")
or tool.Pset.get_element_pset(element, "BBIM_Window")
or tool.Pset.get_element_pset(element, "BBIM_Stair")
):
obj.select_set(False)
continue
if usage_type == "PROFILE":
if len(context.selected_objects) == 1:
bpy.ops.bim.hotkey(hotkey="A_E", description="")
return {"FINISHED"}
else:
self.report({"INFO"}, "Only a single profile-based representation can be edited at a time.")
obj.select_set(False)
continue
if (
tool.Geometry.is_profile_based(obj.data)
or usage_type == "LAYER3"
or tool.Geometry.is_swept_profile(representation)
or tool.Pset.get_element_pset(element, "BBIM_Roof")
or tool.Pset.get_element_pset(element, "BBIM_Railing")
):
if len(context.selected_objects) == 1:
bpy.ops.bim.hotkey(hotkey="S_E", description="")
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):
# Mesh elements with openings must disable openings
@@ -834,6 +919,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 +937,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.Pset.get_element_pset(element, "BBIM_Railing"):
bpy.ops.bim.cad_hotkey(hotkey="S_Q")
elif tool.Pset.get_element_pset(element, "BBIM_Roof"):
bpy.ops.bim.cad_hotkey(hotkey="S_Q")
elif tool.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
@@ -24,10 +24,14 @@ 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"
@@ -33,6 +33,7 @@ classes = (
operator.ToggleGroup,
operator.UnassignGroup,
operator.UpdateGroup,
operator.SelectGroupElements,
prop.ExpandedGroups,
prop.Group,
prop.BIMGroupProperties,
@@ -275,3 +275,19 @@ class UpdateGroup(bpy.types.Operator, tool.Ifc.Operator):
)
bpy.ops.bim.load_groups()
return {"FINISHED"}
class SelectGroupElements(bpy.types.Operator):
bl_idname = "bim.select_group_elements"
bl_label = "Select Group elements"
bl_options = {"REGISTER", "UNDO"}
group: bpy.props.IntProperty()
@classmethod
def poll(cls, context):
return bool(tool.Ifc.get() and context.active_object)
def execute(self, context):
elements = tool.Drawing.get_group_elements(tool.Ifc.get().by_id(self.group))
tool.Spatial.select_products(elements)
return {"FINISHED"}
@@ -107,6 +107,7 @@ class BIM_PT_object_groups(Panel):
for group in ObjectGroupsData.data["groups"]:
row = self.layout.row(align=True)
row.label(text=group["name"])
row.operator("bim.select_group_elements", text="", icon="RESTRICT_SELECT_OFF").group = group["id"]
op = row.operator("bim.unassign_group", text="", icon="X")
op.group = group["id"]
@@ -21,18 +21,28 @@ from . import ui, prop, operator
classes = (
operator.AddFileToRepo,
operator.AddRemote,
operator.AddTag,
operator.CloneRepo,
operator.CommitChanges,
operator.CreateRepo,
operator.DeleteRemote,
operator.DeleteTag,
operator.DiscardUncommitted,
operator.DisplayRevision,
operator.DisplayUncommitted,
operator.Fetch,
operator.Merge,
operator.ObjectLog,
operator.Push,
operator.RefreshGit,
operator.SwitchRevision,
prop.IfcGitTag,
prop.IfcGitListItem,
prop.IfcGitProperties,
ui.IFCGIT_PT_panel,
ui.COMMIT_UL_List,
ui.IFCGIT_PT_revision_inspector,
)
@@ -15,17 +15,29 @@ class IfcGitData:
data = {}
is_loaded = False
@classmethod
def make_sure_is_loaded(cls):
if not cls.is_loaded:
cls.load()
@classmethod
def load(cls):
cls.data = {
"repo": cls.repo(),
"remotes": cls.remotes(),
"branch_names": cls.branch_names(),
"remote_names": cls.remote_names(),
"remote_urls": cls.remote_urls(),
"path_ifc": cls.path_ifc(),
"branches_by_hexsha": cls.branches_by_hexsha(),
"tags_by_hexsha": cls.tags_by_hexsha(),
"name_ifc": cls.name_ifc(),
"dir_name": cls.dir_name(),
"base_name": cls.base_name(),
"working_dir": cls.working_dir(),
"untracked_files": cls.untracked_files(),
"is_detached": cls.is_detached(),
"active_branch_name": cls.active_branch_name(),
"is_dirty": cls.is_dirty(),
"commit": cls.commit(),
"current_revision": cls.current_revision(),
@@ -42,10 +54,28 @@ class IfcGitData:
return tool.IfcGit.repo_from_path(path_ifc)
return None
@classmethod
def remotes(cls):
if cls.repo():
return cls.repo().remotes
return None
@classmethod
def branch_names(cls):
return []
@classmethod
def remote_names(cls):
return []
@classmethod
def remote_urls(cls):
result = {}
if cls.repo():
for remote in cls.repo().remotes:
result[remote.name] = remote.url
return result
@classmethod
def path_ifc(cls):
path_ifc = tool.Ifc.get_path()
@@ -92,6 +122,27 @@ class IfcGitData:
return os.path.basename(path_ifc)
return None
@classmethod
def working_dir(cls):
if cls.repo():
return cls.repo().working_dir
@classmethod
def untracked_files(cls):
if cls.repo():
return cls.repo().untracked_files
return []
@classmethod
def is_detached(cls):
if cls.repo():
return cls.repo().head.is_detached
@classmethod
def active_branch_name(cls):
if cls.repo() and not cls.is_detached():
return cls.repo().active_branch.name
@classmethod
def is_dirty(cls):
if cls.repo() and cls.git_exe():
@@ -15,8 +15,9 @@ class CreateRepo(bpy.types.Operator):
@classmethod
def poll(cls, context):
IfcGitData.make_sure_is_loaded()
path_ifc = IfcGitData.data["path_ifc"]
if not os.path.isfile(path_ifc):
if not path_ifc or not os.path.isfile(path_ifc):
return False
if IfcGitData.data["repo"]:
# repo already exists
@@ -42,8 +43,9 @@ class AddFileToRepo(bpy.types.Operator):
@classmethod
def poll(cls, context):
IfcGitData.make_sure_is_loaded()
path_ifc = IfcGitData.data["path_ifc"]
if not os.path.isfile(path_ifc):
if not path_ifc or not os.path.isfile(path_ifc):
return False
if not IfcGitData.data["repo"]:
# repo doesn't exist
@@ -57,6 +59,34 @@ class AddFileToRepo(bpy.types.Operator):
return {"FINISHED"}
class CloneRepo(bpy.types.Operator):
"""Clone a remote Git repository"""
bl_label = "Clone repository"
bl_idname = "ifcgit.clone_repo"
bl_options = {"REGISTER"}
@classmethod
def poll(cls, context):
props = context.scene.IfcGitProperties
if (
props.remote_url
and props.local_folder
and os.path.isdir(props.local_folder)
and not os.listdir(props.local_folder)
):
return True
return False
def execute(self, context):
props = context.scene.IfcGitProperties
core.clone_repo(tool.IfcGit, props.remote_url, props.local_folder, self)
props.remote_url = ""
refresh()
return {"FINISHED"}
class DiscardUncommitted(bpy.types.Operator):
"""Discard saved changes and update to HEAD"""
@@ -80,6 +110,7 @@ class CommitChanges(bpy.types.Operator):
@classmethod
def poll(cls, context):
IfcGitData.make_sure_is_loaded()
props = context.scene.IfcGitProperties
repo = IfcGitData.data["repo"]
if props.commit_message == "":
@@ -101,7 +132,54 @@ class CommitChanges(bpy.types.Operator):
def execute(self, context):
repo = IfcGitData.data["repo"]
core.commit_changes(tool.IfcGit, tool.Ifc, repo, context)
core.commit_changes(tool.IfcGit, tool.Ifc, repo)
bpy.ops.ifcgit.refresh()
refresh()
return {"FINISHED"}
class AddTag(bpy.types.Operator):
"""Tag selected revision"""
bl_label = "Tag selected revision"
bl_idname = "ifcgit.add_tag"
bl_options = {"REGISTER"}
@classmethod
def poll(cls, context):
IfcGitData.make_sure_is_loaded()
props = context.scene.IfcGitProperties
if props.new_tag_name == "":
return False
repo = IfcGitData.data["repo"]
if repo and (
not tool.IfcGit.is_valid_ref_format(props.new_tag_name)
or props.new_tag_name in [tag.name for tag in repo.tags]
):
return False
return True
def execute(self, context):
repo = IfcGitData.data["repo"]
core.add_tag(tool.IfcGit, repo)
bpy.ops.ifcgit.refresh()
refresh()
return {"FINISHED"}
class DeleteTag(bpy.types.Operator):
"""Delete a tag"""
bl_label = "Delete tag"
bl_idname = "ifcgit.delete_tag"
bl_options = {"REGISTER"}
tag_name: bpy.props.StringProperty()
def execute(self, context):
repo = IfcGitData.data["repo"]
core.delete_tag(tool.IfcGit, repo, self.tag_name)
bpy.ops.ifcgit.refresh()
refresh()
return {"FINISHED"}
@@ -116,8 +194,9 @@ class RefreshGit(bpy.types.Operator):
@classmethod
def poll(cls, context):
IfcGitData.make_sure_is_loaded()
repo = IfcGitData.data["repo"]
if repo != None and repo.heads:
if repo:
return True
return False
@@ -136,9 +215,15 @@ class DisplayRevision(bpy.types.Operator):
bl_idname = "ifcgit.display_revision"
bl_options = {"REGISTER"}
@classmethod
def poll(cls, context):
props = context.scene.IfcGitProperties
if props.ifcgit_commits:
return True
def execute(self, context):
core.colourise_revision(tool.IfcGit, context)
core.colourise_revision(tool.IfcGit)
refresh()
return {"FINISHED"}
@@ -165,6 +250,12 @@ class SwitchRevision(bpy.types.Operator):
bl_idname = "ifcgit.switch_revision"
bl_options = {"REGISTER"}
@classmethod
def poll(cls, context):
props = context.scene.IfcGitProperties
if props.ifcgit_commits:
return True
def execute(self, context):
core.switch_revision(tool.IfcGit, tool.Ifc)
@@ -181,7 +272,9 @@ class Merge(bpy.types.Operator):
@classmethod
def poll(cls, context):
if IfcGitData.data["ifcmerge_exe"]:
IfcGitData.make_sure_is_loaded()
props = context.scene.IfcGitProperties
if IfcGitData.data["ifcmerge_exe"] and props.ifcgit_commits and not IfcGitData.data["is_detached"]:
return True
return False
@@ -192,3 +285,103 @@ class Merge(bpy.types.Operator):
return {"FINISHED"}
else:
return {"CANCELLED"}
class Push(bpy.types.Operator):
"""Pushes the working branch to selected remote"""
bl_label = "Push working branch"
bl_idname = "ifcgit.push"
bl_options = {"REGISTER"}
def execute(self, context):
props = context.scene.IfcGitProperties
repo = IfcGitData.data["repo"]
core.push(tool.IfcGit, repo, props.select_remote, self)
return {"FINISHED"}
class Fetch(bpy.types.Operator):
"""Fetches from the selected remote"""
bl_label = "Fetch from remote"
bl_idname = "ifcgit.fetch"
bl_options = {"REGISTER"}
def execute(self, context):
props = context.scene.IfcGitProperties
repo = IfcGitData.data["repo"]
remote = repo.remotes[props.select_remote]
remote.fetch()
return {"FINISHED"}
class AddRemote(bpy.types.Operator):
"""Add a remote repository"""
bl_label = "Add Remote"
bl_idname = "ifcgit.add_remote"
bl_options = {"REGISTER"}
@classmethod
def poll(cls, context):
IfcGitData.make_sure_is_loaded()
props = context.scene.IfcGitProperties
repo = IfcGitData.data["repo"]
if (
not repo
or not tool.IfcGit.is_valid_ref_format(props.remote_name)
or not props.remote_url
or props.remote_name in [remote.name for remote in repo.remotes]
):
return False
return True
def execute(self, context):
repo = IfcGitData.data["repo"]
core.add_remote(tool.IfcGit, repo)
bpy.ops.ifcgit.refresh()
refresh()
return {"FINISHED"}
class DeleteRemote(bpy.types.Operator):
"""Delete the selected remote"""
bl_label = "Delete Remote"
bl_idname = "ifcgit.delete_remote"
bl_options = {"REGISTER"}
def execute(self, context):
repo = IfcGitData.data["repo"]
core.delete_remote(tool.IfcGit, repo)
bpy.ops.ifcgit.refresh()
refresh()
return {"FINISHED"}
class ObjectLog(bpy.types.Operator):
"""Displays Git log of selected object"""
bl_label = "Selected Object History"
bl_idname = "ifcgit.object_log"
bl_options = {"REGISTER"}
@classmethod
def poll(cls, context):
if not context.active_object:
cls.poll_message_set("No Active Object")
elif not context.active_object.BIMObjectProperties.ifc_definition_id:
cls.poll_message_set("Active Object doesn't have an IFC definition")
else:
return True
def execute(self, context):
step_id = context.active_object.BIMObjectProperties.ifc_definition_id
core.entity_log(tool.IfcGit, tool.Ifc, step_id, self)
return {"FINISHED"}
@@ -7,7 +7,7 @@ from bpy.props import (
IntProperty,
EnumProperty,
)
from blenderbim.bim.module.ifcgit.data import IfcGitData, refresh
from blenderbim.bim.module.ifcgit.data import IfcGitData
def git_branches(self, context):
@@ -21,9 +21,26 @@ def git_branches(self, context):
IfcGitData.data["branch_names"].remove("main")
IfcGitData.data["branch_names"] = ["main"] + IfcGitData.data["branch_names"]
if IfcGitData.data["remotes"]:
for remote in IfcGitData.data["remotes"]:
for remote_branch in remote.refs:
IfcGitData.data["branch_names"].append(remote_branch.name)
return [(myname, myname, myname) for myname in IfcGitData.data["branch_names"]]
def git_remotes(self, context):
"""remotes enum"""
IfcGitData.data["remote_names"] = sorted([remote.name for remote in IfcGitData.data["remotes"]])
if "origin" in IfcGitData.data["remote_names"]:
IfcGitData.data["remote_names"].remove("origin")
IfcGitData.data["remote_names"] = ["origin"] + IfcGitData.data["remote_names"]
return [(myname, myname, myname) for myname in IfcGitData.data["remote_names"]]
def update_revlist(self, context):
"""wrapper to trigger update of the revision list"""
@@ -32,6 +49,19 @@ def update_revlist(self, context):
props.commit_index = 0
class IfcGitTag(PropertyGroup):
"""Properties of a Git tag"""
name: StringProperty(
name="Tag name",
default="",
)
message: StringProperty(
name="Tag message",
default="",
)
class IfcGitListItem(PropertyGroup):
"""Group of properties representing an item in the list."""
@@ -45,6 +75,19 @@ class IfcGitListItem(PropertyGroup):
description="does this commit reference our ifc file",
default=False,
)
author_name: StringProperty(
name="Author Name",
default="",
)
author_email: StringProperty(
name="Author Email",
default="",
)
message: StringProperty(
name="Commit Message",
default="",
)
tags: CollectionProperty(type=IfcGitTag, name="List of revision tags")
class IfcGitProperties(PropertyGroup):
@@ -61,7 +104,34 @@ class IfcGitProperties(PropertyGroup):
description="A short name used to refer to this branch",
default="",
)
new_tag_name: StringProperty(
name="New tag name",
description="A short name used to refer to this tag",
default="",
)
new_tag_message: StringProperty(
name="Tag message (optional)",
description="An optional human readable description of this tag",
default="",
)
remote_name: StringProperty(
name="New remote name",
description="A local name for a remote Git repository",
default="",
)
remote_url: StringProperty(
name="Git URL",
description="A URL pointing to a Git repository",
default="",
)
local_folder: StringProperty(
name="Local folder",
description="A local Git repository path",
default="",
subtype="DIR_PATH",
)
display_branch: EnumProperty(items=git_branches, update=update_revlist)
select_remote: EnumProperty(items=git_remotes)
ifcgit_filter: EnumProperty(
items=[
("all", "All", "All revisions"),
@@ -1,6 +1,6 @@
import bpy
import time
from blenderbim.bim.module.ifcgit.data import IfcGitData, refresh
from blenderbim.bim.module.ifcgit.data import IfcGitData
class IFCGIT_PT_panel(bpy.types.Panel):
@@ -34,8 +34,8 @@ class IFCGIT_PT_panel(bpy.types.Panel):
if path_ifc:
if IfcGitData.data["repo"]:
name_ifc = IfcGitData.data["name_ifc"]
row.label(text=IfcGitData.data["repo"].working_dir, icon="SYSTEM")
if name_ifc in IfcGitData.data["repo"].untracked_files:
row.label(text=IfcGitData.data["working_dir"], icon="SYSTEM")
if name_ifc in IfcGitData.data["untracked_files"]:
row.operator(
"ifcgit.addfile",
text="Add '" + name_ifc + "' to repository",
@@ -54,6 +54,16 @@ class IFCGIT_PT_panel(bpy.types.Panel):
else:
row.label(text="No Git repository found", icon="SYSTEM")
row.label(text="No IFC project saved", icon="FILE")
box = layout.box()
row = box.row()
row.label(text="Clone a remote Git repository")
row = box.row()
row.prop(props, "remote_url")
row = box.row()
row.prop(props, "local_folder")
row = box.row()
row.operator("ifcgit.clone_repo", icon="IMPORT")
return
is_dirty = IfcGitData.data["is_dirty"]
@@ -69,7 +79,7 @@ class IFCGIT_PT_panel(bpy.types.Panel):
row = layout.row()
row.prop(props, "commit_message")
if IfcGitData.data["repo"].head.is_detached:
if IfcGitData.data["is_detached"]:
row = layout.row()
row.label(text="HEAD is detached, commit will create a branch", icon="ERROR")
row.prop(props, "new_branch_name")
@@ -78,10 +88,10 @@ class IFCGIT_PT_panel(bpy.types.Panel):
row.operator("ifcgit.commit_changes", icon="GREASEPENCIL")
row = layout.row()
if IfcGitData.data["repo"].head.is_detached:
if IfcGitData.data["is_detached"]:
row.label(text="Working branch: Detached HEAD")
else:
row.label(text="Working branch: " + IfcGitData.data["repo"].active_branch.name)
row.label(text="Working branch: " + IfcGitData.data["active_branch_name"])
grouped = layout.row()
column = grouped.column()
@@ -110,8 +120,6 @@ class IFCGIT_PT_panel(bpy.types.Panel):
row = column.row()
row.operator("ifcgit.switch_revision", icon="CURRENT_FILE")
# TODO operator to tag selected
row = column.row()
row.operator("ifcgit.merge", icon="EXPERIMENTAL", text="")
@@ -119,7 +127,6 @@ class IFCGIT_PT_panel(bpy.types.Panel):
return
item = props.ifcgit_commits[props.commit_index]
commit = IfcGitData.data["commit"]
if not item.relevant:
row = layout.row()
row.label(text="Revision unrelated to current IFC project", icon="ERROR")
@@ -127,11 +134,50 @@ class IFCGIT_PT_panel(bpy.types.Panel):
box = layout.box()
column = box.column(align=True)
row = column.row()
row.label(text=commit.hexsha)
row.label(text=item.hexsha)
row = column.row()
row.label(text=commit.author.name + " <" + commit.author.email + ">")
row = column.row()
row.label(text=commit.message)
row.label(text=item.author_name + " <" + item.author_email + ">")
for message_line in item.message.split("\n"):
row = column.row()
row.label(text=message_line)
for tag in item.tags:
box = layout.box()
item = box.row()
column = item.column(align=True)
row = column.row()
row.label(text=tag.name)
row.operator("ifcgit.delete_tag", icon="PANEL_CLOSE").tag_name = tag.name
if tag.message:
for message_line in tag.message.split("\n"):
row = column.row()
row.label(text=message_line)
box = layout.box()
row = box.row()
row.prop(props, "new_tag_name")
row = box.row()
row.prop(props, "new_tag_message")
row = box.row()
row.operator("ifcgit.add_tag", icon="GREASEPENCIL")
if IfcGitData.data["remotes"]:
row = layout.row()
row.prop(props, "select_remote", text="Select remote")
urls = IfcGitData.data["remote_urls"]
row.label(text=urls[props.select_remote])
row.operator("ifcgit.delete_remote", text="", icon="PANEL_CLOSE")
row = layout.row()
row.operator("ifcgit.push", icon="EXPORT")
row.operator("ifcgit.fetch", icon="IMPORT")
box = layout.box()
row = box.row()
row.prop(props, "remote_name")
row = box.row()
row.prop(props, "remote_url")
row = box.row()
row.operator("ifcgit.add_remote", icon="ADD")
class COMMIT_UL_List(bpy.types.UIList):
@@ -160,7 +206,36 @@ class COMMIT_UL_List(bpy.types.UIList):
refs += "{" + tag.name + "} "
if commit == current_revision:
layout.label(text="[HEAD] " + refs + commit.message, icon="DECORATE_KEYFRAME")
layout.label(text="[HEAD] " + refs + commit.message.split("\n")[0], icon="DECORATE_KEYFRAME")
else:
layout.label(text=refs + commit.message, icon="DECORATE_ANIMATE")
layout.label(text=refs + commit.message.split("\n")[0], icon="DECORATE_ANIMATE")
layout.label(text=time.strftime("%c", time.localtime(commit.committed_date)))
class IFCGIT_PT_revision_inspector(bpy.types.Panel):
"""Tool panel to interact with revision history"""
bl_idname = "IFCGIT_PT_revision_inspector"
bl_label = "Git History"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "VIEW_3D"
bl_region_type = "UI"
bl_category = "BlenderBIM"
def draw(self, context):
if not IfcGitData.is_loaded:
IfcGitData.load()
layout = self.layout
if not IfcGitData.data["git_exe"]:
row = layout.row()
row.label(text="Git is not installed", icon="ERROR")
return
row = layout.row()
row.operator(
"ifcgit.object_log",
icon="TEXT",
)
@@ -31,13 +31,16 @@ classes = (
operator.ContractMaterialCategory,
operator.CopyMaterial,
operator.DisableEditingAssignedMaterial,
operator.DisableEditingMaterial,
operator.DisableEditingMaterials,
operator.DisableEditingMaterialSetItem,
operator.DisableEditingMaterialSetItemProfile,
operator.DisableEditingMaterials,
operator.EditAssignedMaterial,
operator.EditMaterial,
operator.EditMaterialSetItem,
operator.EditMaterialSetItemProfile,
operator.EnableEditingAssignedMaterial,
operator.EnableEditingMaterial,
operator.EnableEditingMaterialSetItem,
operator.EnableEditingMaterialSetItemProfile,
operator.ExpandMaterialCategory,
@@ -96,6 +96,7 @@ class ObjectMaterialData:
cls.data["total_thickness"] = cls.total_thickness()
cls.data["materials"] = cls.materials()
cls.data["type_material"] = cls.type_material()
cls.data["material_type"] = cls.material_type()
cls.is_loaded = True
@classmethod
@@ -244,3 +245,17 @@ class ObjectMaterialData:
else:
name_attr = "Name"
return getattr(material, name_attr, "Unnamed") or "Unnamed"
@classmethod
def material_type(cls):
material_types = [
"IfcMaterial",
"IfcMaterialConstituentSet",
"IfcMaterialLayerSet",
"IfcMaterialProfileSet",
"IfcMaterialList",
]
version = tool.Ifc.get_schema()
if version == "IFC2X3":
material_types = ["IfcMaterial", "IfcMaterialLayerSet", "IfcMaterialList"]
return [(m, m, ifcopenshell.util.doc.get_entity_doc(version, m).get("description", "")) for m in material_types]
@@ -56,7 +56,37 @@ class SelectByMaterial(bpy.types.Operator, tool.Ifc.Operator):
material: bpy.props.IntProperty()
def _execute(self, context):
core.select_by_material(tool.Material, material=tool.Ifc.get().by_id(self.material))
core.select_by_material(tool.Material, tool.Spatial, material=tool.Ifc.get().by_id(self.material))
class EnableEditingMaterial(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.enable_editing_material"
bl_label = "Enable Editing Material"
bl_options = {"REGISTER", "UNDO"}
material: bpy.props.IntProperty()
def _execute(self, context):
core.enable_editing_material(tool.Material, material=tool.Ifc.get().by_id(self.material))
class EditMaterial(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.edit_material"
bl_label = "Edit Material"
bl_options = {"REGISTER", "UNDO"}
material: bpy.props.IntProperty()
def _execute(self, context):
core.edit_material(tool.Ifc, tool.Material, material=tool.Ifc.get().by_id(self.material))
class DisableEditingMaterial(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.disable_editing_material"
bl_label = "Disable Editing Material"
bl_options = {"REGISTER", "UNDO"}
material: bpy.props.IntProperty()
def _execute(self, context):
core.disable_editing_material(tool.Material)
class AssignParameterizedProfile(bpy.types.Operator, tool.Ifc.Operator):
@@ -144,59 +174,7 @@ class AssignMaterial(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context):
objects = [bpy.data.objects.get(self.obj)] if self.obj else tool.Blender.get_selected_objects()
active_obj = context.active_object
active_object_material_type = self.material_type or active_obj.BIMObjectMaterialProperties.material_type
material = tool.Ifc.get().by_id(int(active_obj.BIMObjectMaterialProperties.material))
for obj in objects:
element = tool.Ifc.get_entity(obj)
if not element:
continue
ifcopenshell.api.run(
"material.assign_material",
tool.Ifc.get(),
product=element,
type=active_object_material_type,
material=material,
)
assigned_material = ifcopenshell.util.element.get_material(element)
if assigned_material.is_a("IfcMaterialLayerSet"):
if not assigned_material.MaterialLayers:
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
layer = ifcopenshell.api.run(
"material.add_layer",
tool.Ifc.get(),
layer_set=assigned_material,
material=tool.Ifc.get().by_type("IfcMaterial")[0],
)
thickness = 0.1 # Arbitrary metric thickness for now
layer.LayerThickness = thickness / unit_scale
elif assigned_material.is_a("IfcMaterialProfileSet"):
if not assigned_material.MaterialProfiles:
named_profiles = [p for p in tool.Ifc.get().by_type("IfcProfileDef") if p.ProfileName]
if named_profiles:
profile = named_profiles[0]
else:
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
size = 0.5 / unit_scale
profile = tool.Ifc.get().create_entity(
"IfcRectangleProfileDef",
ProfileName="New Profile",
ProfileType="AREA",
XDim=size,
YDim=size,
)
material_profile = ifcopenshell.api.run(
"material.add_profile",
tool.Ifc.get(),
profile_set=assigned_material,
material=tool.Ifc.get().by_type("IfcMaterial")[0],
)
ifcopenshell.api.run(
"material.assign_profile",
tool.Ifc.get(),
material_profile=material_profile,
profile=profile,
)
core.assign_material(tool.Ifc, tool.Material, material_type= self.material_type , objects=objects )
class UnassignMaterial(bpy.types.Operator, tool.Ifc.Operator):
@@ -207,12 +185,7 @@ class UnassignMaterial(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context):
objects = [bpy.data.objects.get(self.obj)] if self.obj else tool.Blender.get_selected_objects()
for obj in objects:
element = tool.Ifc.get_entity(obj)
if element:
material = ifcopenshell.util.element.get_material(element, should_inherit=False)
if "Usage" not in material.is_a():
ifcopenshell.api.run("material.unassign_material", tool.Ifc.get(), product=element)
core.unassign_material(tool.Ifc, tool.Material, objects=objects )
class AddConstituent(bpy.types.Operator, tool.Ifc.Operator):
@@ -243,12 +216,12 @@ class RemoveConstituent(bpy.types.Operator, tool.Ifc.Operator):
constituent: bpy.props.IntProperty()
def _execute(self, context):
for inverse in tool.Ifc.get().get_inverse(tool.Ifc.get().by_id(self.constituent)):
if inverse.is_a("IfcMaterialConstituentSet") and len(inverse.MaterialConstituents) == 1:
return
ifcopenshell.api.run(
"material.remove_constituent", tool.Ifc.get(), constituent=tool.Ifc.get().by_id(self.constituent)
)
constituent = tool.Ifc.get().by_id(self.constituent)
for material_set in layer.ToMaterialConstituentSet:
if len(material_set.MaterialConstituents) == 1:
self.report({"ERROR"}, "At least one constituent must exist")
return {"ERROR"}
ifcopenshell.api.run("material.remove_constituent", tool.Ifc.get(), constituent=constituent)
class AddProfile(bpy.types.Operator, tool.Ifc.Operator):
@@ -278,10 +251,12 @@ class RemoveProfile(bpy.types.Operator, tool.Ifc.Operator):
profile: bpy.props.IntProperty()
def _execute(self, context):
for inverse in tool.Ifc.get().get_inverse(tool.Ifc.get().by_id(self.profile)):
if inverse.is_a("IfcMaterialProfileSet") and len(inverse.MaterialProfiles) == 1:
return
ifcopenshell.api.run("material.remove_profile", tool.Ifc.get(), profile=tool.Ifc.get().by_id(self.profile))
profile = tool.Ifc.get().by_id(self.profile)
for material_set in profile.ToMaterialProfileSet:
if len(material_set.MaterialProfiles) == 1:
self.report({"ERROR"}, "At least one profile must exist")
return {"ERROR"}
ifcopenshell.api.run("material.remove_profile", tool.Ifc.get(), profile=profile)
class AddLayer(bpy.types.Operator, tool.Ifc.Operator):
@@ -294,7 +269,7 @@ class AddLayer(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context):
obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
self.file = IfcStore.get_file()
ifcopenshell.api.run(
layer = ifcopenshell.api.run(
"material.add_layer",
self.file,
**{
@@ -303,6 +278,10 @@ class AddLayer(bpy.types.Operator, tool.Ifc.Operator):
},
)
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
thickness = 0.1 # Arbitrary metric thickness for now
layer.LayerThickness = thickness / unit_scale
class ReorderMaterialSetItem(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.reorder_material_set_item"
@@ -336,10 +315,12 @@ class RemoveLayer(bpy.types.Operator, tool.Ifc.Operator):
layer: bpy.props.IntProperty()
def _execute(self, context):
for inverse in tool.Ifc.get().get_inverse(tool.Ifc.get().by_id(self.layer)):
if inverse.is_a("IfcMaterialLayerSet") and len(inverse.MaterialLayers) == 1:
return
ifcopenshell.api.run("material.remove_layer", tool.Ifc.get(), layer=tool.Ifc.get().by_id(self.layer))
layer = tool.Ifc.get().by_id(self.layer)
for material_set in layer.ToMaterialLayerSet:
if len(material_set.MaterialLayers) == 1:
self.report({"ERROR"}, "At least one layer must exist")
return {"ERROR"}
ifcopenshell.api.run("material.remove_layer", tool.Ifc.get(), layer=layer)
class AddListItem(bpy.types.Operator, tool.Ifc.Operator):
@@ -85,24 +85,10 @@ def get_materials(self, context):
return ObjectMaterialData.data["materials"]
def get_object_material_types(self, context):
global materialtypes_enum
if len(materialtypes_enum) == 0 and IfcStore.get_file():
material_types = [
"IfcMaterial",
"IfcMaterialConstituentSet",
"IfcMaterialLayerSet",
"IfcMaterialLayerSetUsage",
"IfcMaterialProfileSet",
"IfcMaterialProfileSetUsage",
"IfcMaterialList",
]
version = tool.Ifc.get_schema()
if version == "IFC2X3":
material_types = ["IfcMaterial", "IfcMaterialLayerSet", "IfcMaterialLayerSetUsage", "IfcMaterialList"]
materialtypes_enum.clear()
materialtypes_enum = [(m, m, get_entity_doc(version, m).get("description", "")) for m in material_types]
return materialtypes_enum
def get_object_material_type(self, context):
if not ObjectMaterialData.is_loaded:
ObjectMaterialData.load()
return ObjectMaterialData.data["material_type"]
def get_material_types(self, context):
@@ -131,10 +117,13 @@ class BIMMaterialProperties(PropertyGroup):
materials: CollectionProperty(name="Materials", type=Material)
active_material_index: IntProperty(name="Active Material Index")
profiles: EnumProperty(items=get_profiles, name="Profiles")
active_material_id: IntProperty(name="Active Material ID")
material_attributes: CollectionProperty(name="Material Attributes", type=Attribute)
editing_material_type = StringProperty(name="Editing Material Type")
class BIMObjectMaterialProperties(PropertyGroup):
material_type: EnumProperty(items=get_object_material_types, name="Material Type")
material_type: EnumProperty(items=get_object_material_type, name="Material Type")
material: EnumProperty(items=get_materials, name="Material")
is_editing: BoolProperty(name="Is Editing", default=False)
material_set_usage_attributes: CollectionProperty(name="Material Set Usage Attributes", type=Attribute)
@@ -57,13 +57,21 @@ class BIM_PT_materials(Panel):
row.alignment = "RIGHT"
if self.props.material_type == "IfcMaterial":
row.operator("bim.add_material", text="", icon="ADD")
if self.props.materials and self.props.active_material_index < len(self.props.materials):
material = self.props.materials[self.props.active_material_index]
if material.ifc_definition_id:
op = row.operator("bim.select_by_material", text="", icon="RESTRICT_SELECT_OFF")
op.material = material.ifc_definition_id
row.operator("bim.remove_material", text="", icon="X").material = material.ifc_definition_id
if self.props.active_material_id:
row.operator("bim.edit_material", text="", icon="CHECKMARK").material = material.ifc_definition_id
row.operator("bim.disable_editing_material", text="", icon="CANCEL").material = material.ifc_definition_id
self.draw_editable_material_attributes_ui()
else:
row.operator("bim.add_material", text="", icon="ADD")
op = row.operator("bim.select_by_material", text="", icon="RESTRICT_SELECT_OFF")
op.material = material.ifc_definition_id
row.operator("bim.enable_editing_material", text="", icon="GREASEPENCIL").material = material.ifc_definition_id
row.operator("bim.remove_material", text="", icon="X").material = material.ifc_definition_id
else:
row.operator("bim.add_material", text="", icon="ADD")
else:
row.operator("bim.add_material_set", text="", icon="ADD").set_type = self.props.material_type
if self.props.materials and self.props.active_material_index < len(self.props.materials):
@@ -76,6 +84,9 @@ class BIM_PT_materials(Panel):
self.layout.template_list("BIM_UL_materials", "", self.props, "materials", self.props, "active_material_index")
def draw_editable_material_attributes_ui(self):
blenderbim.bim.helper.draw_attributes(self.props.material_attributes, self.layout)
class BIM_PT_material(Panel):
bl_label = "IFC Material"
bl_idname = "BIM_PT_material"
@@ -144,8 +155,7 @@ class BIM_PT_object_material(Panel):
row = self.layout.row(align=True)
prop_with_search(row, self.props, "material_type", text="")
if self.props.material_type == "IfcMaterial" or self.props.material_type == "IfcMaterialList":
prop_with_search(row, self.props, "material", text="")
prop_with_search(row, self.props, "material", text="")
row.operator("bim.assign_material", icon="ADD", text="")
def draw_material_ui(self):
@@ -30,7 +30,6 @@ class BIM_PT_misc_utilities(bpy.types.Panel):
def draw(self, context):
layout = self.layout
props = context.scene.BIMMiscProperties
row = layout.split(factor=0.2, align=True)
row.prop(props, "override_colour", text="")
row.operator("bim.set_override_colour")
@@ -49,6 +48,8 @@ class BIM_PT_misc_utilities(bpy.types.Panel):
row.operator("bim.draw_system_arrows")
row = layout.row()
row.operator("bim.clean_wireframes")
row = layout.row()
row.operator("bim.patch_non_parametric_mep_segment")
row = layout.row(align=True)
row.operator("bim.enable_editing_sketch_extrusion_profile", text="Start Sketching")
@@ -86,6 +86,7 @@ classes = (
profile.ExtendProfile,
profile.RecalculateProfile,
profile.Rotate90,
profile.PatchNonParametricMepSegment,
roof.GenerateHippedRoof,
slab.DisableEditingExtrusionProfile,
slab.DisableEditingSketchExtrusionProfile,
@@ -96,6 +97,8 @@ classes = (
slab.ResetVertex,
slab.SetArcIndex,
space.GenerateSpace,
space.GenerateSpacesFromWalls,
space.ToggleSpaceVisibility,
prop.BIMModelProperties,
prop.BIMArrayProperties,
prop.BIMStairProperties,
@@ -104,7 +107,6 @@ classes = (
prop.BIMDoorProperties,
prop.BIMRailingProperties,
prop.BIMRoofProperties,
ui.BIM_PT_authoring,
ui.BIM_PT_array,
ui.BIM_PT_stair,
ui.BIM_PT_sverchok,
@@ -114,6 +116,7 @@ classes = (
ui.BIM_PT_roof,
ui.LaunchTypeManager,
ui.BIM_MT_model,
ui.BIM_PT_Grids,
grid.BIM_OT_add_object,
stair.BIM_OT_add_object,
stair.BIM_OT_add_clever_stair,
@@ -86,9 +86,10 @@ class EnableEditingArray(bpy.types.Operator, tool.Ifc.Operator):
data = json.loads(ifcopenshell.util.element.get_pset(element, "BBIM_Array", "Data"))[self.item]
props = obj.BIMArrayProperties
props.count = data["count"]
props.x = data["x"]
props.y = data["y"]
props.z = data["z"]
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
props.x = data["x"] * si_conversion
props.y = data["y"] * si_conversion
props.z = data["z"] * si_conversion
props.use_local_space = data.get("use_local_space", False)
props.sync_children = data.get("sync_children", False)
props.method = data.get("method", "OFFSET")
@@ -106,15 +107,16 @@ class EditArray(bpy.types.Operator, tool.Ifc.Operator):
obj = context.active_object
element = tool.Ifc.get_entity(obj)
props = obj.BIMArrayProperties
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
pset = ifcopenshell.util.element.get_pset(element, "BBIM_Array")
data = json.loads(pset["Data"])
data[self.item] = {
"children": data[self.item]["children"],
"count": props.count,
"x": props.x,
"y": props.y,
"z": props.z,
"x": props.x / si_conversion,
"y": props.y / si_conversion,
"z": props.z / si_conversion,
"use_local_space": props.use_local_space,
"sync_children": props.sync_children,
"method": props.method,
@@ -187,6 +189,7 @@ class SelectArrayParent(bpy.types.Operator):
obj.select_set(True)
return {"FINISHED"}
class Input3DCursorXArray(bpy.types.Operator):
bl_idname = "bim.input_cursor_x_array"
bl_label = "Get 3d Cursor X Input for Array"
@@ -202,6 +205,7 @@ class Input3DCursorXArray(bpy.types.Operator):
props.x = cursor.location.x - obj.location.x
return {"FINISHED"}
class Input3DCursorYArray(bpy.types.Operator):
bl_idname = "bim.input_cursor_y_array"
bl_label = "Get 3d Cursor Y Input for Array"
@@ -217,6 +221,7 @@ class Input3DCursorYArray(bpy.types.Operator):
props.y = cursor.location.y - obj.location.y
return {"FINISHED"}
class Input3DCursorZArray(bpy.types.Operator):
bl_idname = "bim.input_cursor_z_array"
bl_label = "Get 3d Cursor Z Input for Array"
@@ -59,9 +59,19 @@ class AuthoringData:
cls.data["type_thumbnail"] = cls.type_thumbnail()
cls.data["is_voidable_element"] = cls.is_voidable_element()
cls.data["has_visible_openings"] = cls.has_visible_openings()
cls.data["has_visible_boundaries"] = cls.has_visible_boundaries()
cls.data["active_class"] = cls.active_class()
cls.data["active_material_usage"] = cls.active_material_usage()
cls.data["active_representation_type"] = cls.active_representation_type()
cls.data["boundary_class"] = cls.boundary_class()
@classmethod
def boundary_class(cls):
declaration = tool.Ifc.schema().declaration_by_name("IfcRelSpaceBoundary")
declarations = ifcopenshell.util.schema.get_subtypes(declaration)
names = [d.name() for d in declarations]
version = tool.Ifc.get_schema()
return [(c, c, get_entity_doc(version, c).get("description", "")) for c in sorted(names)]
@classmethod
def type_class(cls):
@@ -69,7 +79,10 @@ class AuthoringData:
declarations = ifcopenshell.util.schema.get_subtypes(declaration)
names = [d.name() for d in declarations]
declaration = tool.Ifc.schema().declaration_by_name("IfcSpatialElementType")
if tool.Ifc.get_schema() == "IFC2X3":
declaration = tool.Ifc.schema().declaration_by_name("IfcSpatialStructureElementType")
else:
declaration = tool.Ifc.schema().declaration_by_name("IfcSpatialElementType")
declarations = ifcopenshell.util.schema.get_subtypes(declaration)
names.extend([d.name() for d in declarations])
@@ -161,6 +174,17 @@ class AuthoringData:
return True
return False
@classmethod
def has_visible_boundaries(cls):
element = tool.Ifc.get_entity(bpy.context.active_object)
if element:
if element.is_a("IfcRelSpaceBoundary"):
return True
for boundary in getattr(element, "BoundedBy", []):
if tool.Ifc.get_object(boundary):
return True
return False
@classmethod
def active_class(cls):
element = tool.Ifc.get_entity(bpy.context.active_object)
@@ -177,7 +201,7 @@ class AuthoringData:
def active_representation_type(cls):
if bpy.context.active_object:
representation = tool.Geometry.get_active_representation(bpy.context.active_object)
if representation:
if representation and representation.is_a("IfcShapeRepresentation"):
return representation.RepresentationType
@classmethod
@@ -82,14 +82,14 @@ class ProfileDecorator:
bmesh.ops.triangulate(traingulated_bm, faces=traingulated_bm.faces)
face_indices = [[v.index for v in f.verts] for f in traingulated_bm.faces]
faces_color = transparent_color(self.model_props.decorator_color_special)
faces_color = transparent_color(self.addon_prefs.decorator_color_special)
self.draw_batch("TRIS", vertices_coords, faces_color, face_indices)
def __call__(self, context, get_custom_bmesh=None, draw_faces=False, exit_edit_mode_callback=None):
self.model_props = context.scene.BIMModelProperties
selected_elements_color = self.model_props.decorator_color_selected
unselected_elements_color = self.model_props.decorator_color_unselected
special_elements_color = self.model_props.decorator_color_special
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
obj = context.active_object
@@ -214,8 +214,8 @@ class ProfileDecorator:
self.draw_batch("POINTS", unselected_vertices, transparent_color(unselected_elements_color, 0.5))
self.draw_batch("POINTS", error_vertices, ERROR_ELEMENTS_COLOR)
self.draw_batch("POINTS", special_vertices, self.model_props.decorator_color_special)
self.draw_batch("POINTS", selected_vertices, self.model_props.decorator_color_selected)
self.draw_batch("POINTS", special_vertices, special_elements_color)
self.draw_batch("POINTS", selected_vertices, selected_elements_color)
# Draw arcs
arc_centroids = []
@@ -18,7 +18,6 @@
import bpy
from bpy.types import Operator
import bmesh
from bmesh.types import BMVert, BMFace
@@ -27,8 +26,6 @@ from ifcopenshell.util.shape_builder import V
import blenderbim
import blenderbim.tool as tool
import blenderbim.core.geometry as core
from blenderbim.bim.helper import convert_property_group_from_si
from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.module.model.window import create_bm_window, create_bm_box, update_simple_openings
from mathutils import Vector, Matrix
@@ -44,32 +41,31 @@ def update_door_modifier_representation(context):
element = tool.Ifc.get_entity(obj)
ifc_file = tool.Ifc.get()
sliding_door = "SLIDING" in props.door_type
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
representation_data = {
"operation_type": props.door_type,
"overall_height": props.overall_height,
"overall_width": props.overall_width,
"overall_height": props.overall_height / si_conversion,
"overall_width": props.overall_width / si_conversion,
"lining_properties": {
"LiningDepth": props.lining_depth,
"LiningThickness": props.lining_thickness,
"LiningOffset": props.lining_offset,
"LiningToPanelOffsetX": props.lining_to_panel_offset_x,
"LiningToPanelOffsetY": props.lining_to_panel_offset_y,
"TransomThickness": props.transom_thickness,
"TransomOffset": props.transom_offset,
"transomThickness": props.transom_thickness,
"TransomOffset": props.transom_offset,
"CasingThickness": props.casing_thickness,
"CasingDepth": props.casing_depth,
"ThresholdThickness": props.threshold_thickness,
"ThresholdDepth": props.threshold_depth,
"ThresholdOffset": props.threshold_offset,
"LiningDepth": props.lining_depth / si_conversion,
"LiningThickness": props.lining_thickness / si_conversion,
"LiningOffset": props.lining_offset / si_conversion,
"LiningToPanelOffsetX": props.lining_to_panel_offset_x / si_conversion,
"LiningToPanelOffsetY": props.lining_to_panel_offset_y / si_conversion,
"TransomThickness": props.transom_thickness / si_conversion,
"TransomOffset": props.transom_offset / si_conversion,
"CasingThickness": props.casing_thickness / si_conversion,
"CasingDepth": props.casing_depth / si_conversion,
"ThresholdThickness": props.threshold_thickness / si_conversion,
"ThresholdDepth": props.threshold_depth / si_conversion,
"ThresholdOffset": props.threshold_offset / si_conversion,
},
"panel_properties": {
"PanelDepth": props.panel_depth,
"PanelDepth": props.panel_depth / si_conversion,
"PanelWidth": props.panel_width_ratio,
"FrameDepth": props.frame_depth,
"FrameThickness": props.frame_thickness,
"FrameDepth": props.frame_depth / si_conversion,
"FrameThickness": props.frame_thickness / si_conversion,
},
}
@@ -132,15 +128,16 @@ def update_door_modifier_representation(context):
)
# type attributes
element.OperationType = props.door_type
if tool.Ifc.get_schema() != "IFC2X3":
element.OperationType = props.door_type
# occurences attributes
occurences = tool.Ifc.get_all_element_occurences(element)
for occurence in occurences:
occurence.OverallWidth = props.overall_width
occurence.OverallHeight = props.overall_height
occurence.OverallWidth = props.overall_width / si_conversion
occurence.OverallHeight = props.overall_height / si_conversion
update_simple_openings(element, props.overall_width, props.overall_height)
update_simple_openings(element, props.overall_width / si_conversion, props.overall_height / si_conversion)
# TODO: move it out to tools
@@ -281,27 +278,26 @@ def create_bm_door_lining(bm, size: Vector, thickness: list, position: Vector =
def update_door_modifier_bmesh(context):
obj = context.object
props = obj.BIMDoorProperties
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
overall_width = props.overall_width * si_conversion
overall_height = props.overall_height * si_conversion
overall_width = props.overall_width
overall_height = props.overall_height
door_type = props.door_type
double_swing_door = "DOUBLE_SWING" in door_type
double_door = "DOUBLE_DOOR" in door_type
sliding_door = "SLIDING" in door_type
# lining params
lining_depth = props.lining_depth * si_conversion
lining_thickness_default = props.lining_thickness * si_conversion
lining_offset = props.lining_offset * si_conversion
lining_depth = props.lining_depth
lining_thickness_default = props.lining_thickness
lining_offset = props.lining_offset
lining_to_panel_offset_x = (
props.lining_to_panel_offset_x * si_conversion if not sliding_door else lining_thickness_default
props.lining_to_panel_offset_x if not sliding_door else lining_thickness_default
)
panel_depth = props.panel_depth * si_conversion
lining_to_panel_offset_y = props.lining_to_panel_offset_y * si_conversion if not sliding_door else -panel_depth
panel_depth = props.panel_depth
lining_to_panel_offset_y = props.lining_to_panel_offset_y if not sliding_door else -panel_depth
transom_thickness = props.transom_thickness * si_conversion / 2
transfom_offset = props.transom_offset * si_conversion
transom_thickness = props.transom_thickness / 2
transfom_offset = props.transom_offset
if transom_thickness == 0:
transfom_offset = 0
window_lining_height = overall_height - transfom_offset - transom_thickness
@@ -316,24 +312,24 @@ def update_door_modifier_bmesh(context):
side_lining_thickness = side_lining_thickness - panel_lining_overlap_x
top_lining_thickness = top_lining_thickness - panel_top_lining_overlap_x
threshold_thickness = props.threshold_thickness * si_conversion
threshold_depth = props.threshold_depth * si_conversion
threshold_offset = props.threshold_offset * si_conversion
threshold_thickness = props.threshold_thickness
threshold_depth = props.threshold_depth
threshold_offset = props.threshold_offset
threshold_width = overall_width - side_lining_thickness * 2
casing_thickness = props.casing_thickness * si_conversion
casing_depth = props.casing_depth * si_conversion
casing_thickness = props.casing_thickness
casing_depth = props.casing_depth
# panel params
panel_width = door_opening_width * props.panel_width_ratio
frame_depth = props.frame_depth * si_conversion
frame_thickness = props.frame_thickness * si_conversion
frame_depth = props.frame_depth
frame_thickness = props.frame_thickness
frame_height = window_lining_height - lining_to_panel_offset_x * 2
glass_thickness = 0.01 * si_conversion
glass_thickness = 0.01
# handle dimensions (hardcoded)
handle_size = V(120, 40, 20) * 0.001 * si_conversion
handle_offset = V(60, 0, 1000) * 0.001 * si_conversion # to the handle center
handle_size = V(120, 40, 20) * 0.001
handle_offset = V(60, 0, 1000) * 0.001 # to the handle center
handle_center_offset = V(handle_size.y / 2, 0, handle_size.z) / 2
if transfom_offset:
@@ -418,7 +414,7 @@ def update_door_modifier_bmesh(context):
if double_door:
# keeping a little space between doors for readibility
double_door_offset = 0.001 * si_conversion
double_door_offset = 0.001
panel_size.x = panel_size.x / 2 - double_door_offset
door_verts.extend(create_bm_door_panel(panel_size, panel_position, "LEFT"))
@@ -488,10 +484,15 @@ class BIM_OT_add_door(bpy.types.Operator, tool.Ifc.Operator):
mesh = bpy.data.meshes.new("IfcDoor")
obj = bpy.data.objects.new("IfcDoor", mesh)
obj.location = spawn_location
collection = context.view_layer.active_layer_collection.collection
collection.objects.link(obj)
element = blenderbim.core.root.assign_class(
tool.Ifc, tool.Collector, tool.Root, obj=obj, ifc_class="IfcDoor", should_add_representation=False
)
element.PredefinedType = "DOOR"
blenderbim.core.geometry.edit_object_placement(tool.Ifc, tool.Geometry, tool.Surveyor, obj=obj)
if tool.Ifc.get_schema() != "IFC2X3":
element.PredefinedType = "DOOR"
bpy.ops.object.select_all(action="DESELECT")
bpy.context.view_layer.objects.active = None
@@ -512,18 +513,13 @@ class AddDoor(bpy.types.Operator, tool.Ifc.Operator):
element = tool.Ifc.get_entity(obj)
props = obj.BIMDoorProperties
if element.is_a() not in ("IfcDoor", "IfcDoorType"):
self.report({"ERROR"}, "Object has to be IfcDoor/IfcDoorType type to add a door.")
if element.is_a() not in ("IfcDoor", "IfcDoorType", "IfcDoorStyle"):
self.report({"ERROR"}, "Object has to be IfcDoor/IfcDoorType/IfcDoorStyle type to add a door.")
return {"CANCELLED"}
# need to make sure all default props will have correct units
if not props.door_added_previously:
skip_props = ("is_editing", "door_type", "door_added_previously", "panel_width_ratio")
convert_property_group_from_si(props, skip_props=skip_props)
door_data = props.get_general_kwargs()
lining_props = props.get_lining_kwargs()
panel_props = props.get_panel_kwargs()
door_data = props.get_general_kwargs(convert_to_project_units=True)
lining_props = props.get_lining_kwargs(convert_to_project_units=True)
panel_props = props.get_panel_kwargs(convert_to_project_units=True)
door_data["lining_properties"] = lining_props
door_data["panel_properties"] = panel_props
@@ -550,11 +546,13 @@ class CancelEditingDoor(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context):
obj = context.active_object
element = tool.Ifc.get_entity(obj)
data = json.loads(ifcopenshell.util.element.get_pset(element, "BBIM_Door", "Data"))
props = obj.BIMDoorProperties
data = json.loads(ifcopenshell.util.element.get_pset(element, "BBIM_Door", "Data"))
data.update(data.pop("lining_properties"))
data.update(data.pop("panel_properties"))
# restore previous settings since editing was canceled
for prop_name in data:
setattr(props, prop_name, data[prop_name])
props.set_props_kwargs_from_ifc_data(data)
body = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
blenderbim.core.geometry.switch_representation(
@@ -581,9 +579,9 @@ class FinishEditingDoor(bpy.types.Operator, tool.Ifc.Operator):
element = tool.Ifc.get_entity(obj)
props = obj.BIMDoorProperties
door_data = props.get_general_kwargs()
lining_props = props.get_lining_kwargs()
panel_props = props.get_panel_kwargs()
door_data = props.get_general_kwargs(convert_to_project_units=True)
lining_props = props.get_lining_kwargs(convert_to_project_units=True)
panel_props = props.get_panel_kwargs(convert_to_project_units=True)
door_data["lining_properties"] = lining_props
door_data["panel_properties"] = panel_props
@@ -612,15 +610,7 @@ class EnableEditingDoor(bpy.types.Operator, tool.Ifc.Operator):
data.update(data.pop("panel_properties"))
# required since we could load pset from .ifc and BIMDoorProperties won't be set
for prop_name in data:
setattr(props, prop_name, data[prop_name])
# need to make sure all props that weren't used before
# will have correct units
skip_props = ("is_editing", "door_type", "door_added_previously", "panel_width_ratio")
skip_props += tuple(data.keys())
convert_property_group_from_si(props, skip_props=skip_props)
props.set_props_kwargs_from_ifc_data(data)
props.is_editing = 1
return {"FINISHED"}
@@ -638,7 +628,6 @@ class RemoveDoor(bpy.types.Operator, tool.Ifc.Operator):
pset = tool.Pset.get_element_pset(element, "BBIM_Door")
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), pset=pset)
props.door_added_previously = True
return {"FINISHED"}
@@ -30,32 +30,20 @@ def add_object(self, context):
obj = bpy.data.objects.new("Grid", None)
obj.name = "Grid"
collection = bpy.data.collections.new(obj.name)
has_site_collection = False
for child in context.view_layer.layer_collection.children:
if "IfcProject/" not in child.name:
continue
for grandchild in child.children:
if "IfcSite/" not in grandchild.name:
continue
has_site_collection = True
grandchild.collection.children.link(collection)
break
if not has_site_collection:
context.view_layer.active_layer_collection.collection.children.link(collection)
collection = context.view_layer.active_layer_collection.collection
site = tool.Ifc.get().by_type("IfcSite")
if site:
site = site[0]
site_obj = tool.Ifc.get_object(site)
if site_obj:
collection = site_obj.BIMObjectProperties.collection or collection
collection.objects.link(obj)
self.file = IfcStore.get_file()
if self.file:
bpy.ops.bim.assign_class(obj=obj.name, ifc_class="IfcGrid")
collection.name = obj.name
grid = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
if has_site_collection:
site_obj = bpy.data.objects.get(grandchild.name)
if site_obj and site_obj.BIMObjectProperties.ifc_definition_id:
blenderbim.core.spatial.assign_container(
tool.Ifc, tool.Collector, tool.Spatial, structure_obj=site_obj, element_obj=obj
)
bpy.ops.bim.assign_class(obj=obj.name, ifc_class="IfcGrid")
grid = tool.Ifc.get_entity(obj)
collection = obj.BIMObjectProperties.collection or collection
axes_collection = bpy.data.collections.new("UAxes")
collection.children.link(axes_collection)
@@ -73,15 +61,14 @@ def add_object(self, context):
axes_collection.objects.link(obj)
if self.file:
result = ifcopenshell.api.run(
"grid.create_grid_axis",
self.file,
**{"axis_tag": tag, "uvw_axes": "UAxes", "grid": grid},
)
IfcStore.link_element(result, obj)
ifcopenshell.api.run("grid.create_axis_curve", self.file, **{"axis_curve": obj, "grid_axis": result})
obj.BIMObjectProperties.ifc_definition_id = result.id()
result = ifcopenshell.api.run(
"grid.create_grid_axis",
tool.Ifc.get(),
**{"axis_tag": tag, "uvw_axes": "UAxes", "grid": grid},
)
IfcStore.link_element(result, obj)
ifcopenshell.api.run("grid.create_axis_curve", tool.Ifc.get(), **{"axis_curve": obj, "grid_axis": result})
obj.BIMObjectProperties.ifc_definition_id = result.id()
axes_collection = bpy.data.collections.new("VAxes")
collection.children.link(axes_collection)
@@ -99,18 +86,17 @@ def add_object(self, context):
axes_collection.objects.link(obj)
if self.file:
result = ifcopenshell.api.run(
"grid.create_grid_axis",
self.file,
**{"axis_tag": tag, "uvw_axes": "VAxes", "grid": grid},
)
IfcStore.link_element(result, obj)
ifcopenshell.api.run("grid.create_axis_curve", self.file, **{"axis_curve": obj, "grid_axis": result})
obj.BIMObjectProperties.ifc_definition_id = result.id()
result = ifcopenshell.api.run(
"grid.create_grid_axis",
tool.Ifc.get(),
**{"axis_tag": tag, "uvw_axes": "VAxes", "grid": grid},
)
IfcStore.link_element(result, obj)
ifcopenshell.api.run("grid.create_axis_curve", tool.Ifc.get(), **{"axis_curve": obj, "grid_axis": result})
obj.BIMObjectProperties.ifc_definition_id = result.id()
class BIM_OT_add_object(Operator):
class BIM_OT_add_object(Operator, tool.Ifc.Operator):
bl_idname = "mesh.add_grid"
bl_label = "IFC Grid"
bl_options = {"REGISTER", "UNDO"}
@@ -122,10 +108,7 @@ class BIM_OT_add_object(Operator):
@classmethod
def poll(cls, context):
return IfcStore.get_file()
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
return tool.Ifc.get()
def _execute(self, context):
add_object(self, context)
@@ -232,9 +232,16 @@ class FilledOpeningGenerator:
curve_3d = ifcopenshell.util.representation.resolve_representation(profile).Items[0]
def get_curve_2d_from_3d(curve_3d):
ifc_segments = [shape_builder.deep_copy(s) for s in curve_3d.Segments]
ifc_points = ifc_file.createIfcCartesianPointList2D([Vector(p).xz for p in curve_3d.Points.CoordList])
ifc_curve = ifc_file.createIfcIndexedPolyCurve(Points=ifc_points, Segments=ifc_segments)
if tool.Ifc.get_schema() == "IFC2X3":
coords = [Vector(p).xz for p in shape_builder.get_polyline_coords(curve_3d)]
ifc_curve = shape_builder.polyline(coords, closed=True)
else:
# using different algorithm to keep arc segments possible in the future
ifc_segments = [shape_builder.deep_copy(s) for s in curve_3d.Segments]
ifc_points = ifc_file.createIfcCartesianPointList2D(
[Vector(p).xz for p in curve_3d.Points.CoordList]
)
ifc_curve = ifc_file.createIfcIndexedPolyCurve(Points=ifc_points, Segments=ifc_segments)
return ifc_curve
extrusion = shape_builder.extrude(
@@ -657,7 +664,7 @@ class ShowOpenings(Operator, tool.Ifc.Operator):
props = bpy.context.scene.BIMModelProperties
for obj in context.selected_objects:
element = tool.Ifc.get_entity(obj)
if not element:
if not element or not getattr(element, "HasOpenings", None):
continue
if tool.Ifc.is_moved(obj):
blenderbim.core.geometry.edit_object_placement(tool.Ifc, tool.Geometry, tool.Surveyor, obj=obj)
@@ -682,7 +689,6 @@ class HideOpenings(Operator, tool.Ifc.Operator):
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
props = bpy.context.scene.BIMModelProperties
to_delete = set()
for obj in context.selected_objects:
element = tool.Ifc.get_entity(obj)
@@ -694,7 +700,7 @@ class HideOpenings(Operator, tool.Ifc.Operator):
if opening_obj:
to_delete.add(opening_obj)
for opening_obj in to_delete:
tool.Ifc.unlink(element=opening, obj=opening_obj)
tool.Ifc.unlink(obj=opening_obj)
bpy.data.objects.remove(opening_obj)
tool.Model.clear_scene_openings()
return {"FINISHED"}
@@ -793,10 +799,10 @@ class DecorationsHandler:
batch.draw(shader)
def __call__(self, context):
self.model_props = context.scene.BIMModelProperties
selected_elements_color = self.model_props.decorator_color_selected
unselected_elements_color = self.model_props.decorator_color_unselected
special_elements_color = self.model_props.decorator_color_special
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]
@@ -19,6 +19,7 @@
import bpy
import blenderbim.tool as tool
import blenderbim.core.spatial
import blenderbim.core.aggregate
from blenderbim.bim.ifc import IfcStore
@@ -67,7 +68,7 @@ class PieUpdateContainer(bpy.types.Operator):
if not obj.BIMObjectProperties.ifc_definition_id:
continue
for collection in obj.users_collection:
spatial_obj = bpy.data.objects.get(collection.name)
spatial_obj = collection.BIMCollectionProperties.obj
if spatial_obj and spatial_obj.BIMObjectProperties.ifc_definition_id:
blenderbim.core.spatial.assign_container(
tool.Ifc, tool.Collector, tool.Spatial, structure_obj=spatial_obj, element_obj=obj
@@ -86,6 +87,8 @@ class VIEW3D_MT_PIE_bim(bpy.types.Menu):
pie.operator("bim.pie_add_opening")
pie.operator("bim.pie_update_container")
pie.operator("bim.open_pie_class", text="Assign IFC Class")
pie.operator("bim.assign_object", text ="Assign Aggregation")
pie.operator("bim.unassign_object", text ="Unassign Aggregation")
class VIEW3D_MT_PIE_bim_class(bpy.types.Menu):
@@ -37,12 +37,6 @@ from . import prop
import json
def select_and_activate_single_object(context, obj):
bpy.ops.object.select_all(action="DESELECT")
context.view_layer.objects.active = obj
obj.select_set(True)
class AddEmptyType(bpy.types.Operator, AddObjectHelper):
bl_idname = "bim.add_empty_type"
bl_label = "Add Empty Type"
@@ -52,7 +46,7 @@ class AddEmptyType(bpy.types.Operator, AddObjectHelper):
obj = bpy.data.objects.new("TYPEX", None)
context.scene.collection.objects.link(obj)
context.scene.BIMRootProperties.ifc_product = "IfcElementType"
select_and_activate_single_object(context, obj)
tool.Blender.select_and_activate_single_object(context, obj)
return {"FINISHED"}
@@ -97,7 +91,7 @@ class AddConstrTypeInstance(bpy.types.Operator):
return {"FINISHED"}
elif material and material.is_a("IfcMaterialLayerSet"):
if self.generate_layered_element(ifc_class, relating_type):
select_and_activate_single_object(context, context.selected_objects[-1])
tool.Blender.select_and_activate_single_object(context, context.selected_objects[-1])
return {"FINISHED"}
if relating_type.is_a("IfcFlowSegmentType") and not relating_type.RepresentationMaps:
if mep.MepGenerator(relating_type).generate():
@@ -133,9 +127,10 @@ class AddConstrTypeInstance(bpy.types.Operator):
obj = bpy.data.objects.new(tool.Model.generate_occurrence_name(relating_type, instance_class), mesh)
obj.location = context.scene.cursor.location
collection = context.view_layer.active_layer_collection.collection
collection.objects.link(obj)
collection_obj = bpy.data.objects.get(collection.name)
collection_obj = collection.BIMCollectionProperties.obj
bpy.ops.bim.assign_class(obj=obj.name, ifc_class=instance_class)
element = tool.Ifc.get_entity(obj)
@@ -144,6 +139,26 @@ class AddConstrTypeInstance(bpy.types.Operator):
# Update required as core.type.assign_type may change obj.data
context.view_layer.update()
if (
building_obj
and building_element
and building_element.is_a() in ["IfcWall", "IfcWallStandardCase", "IfcCovering"]
and instance_class in ["IfcWindow", "IfcDoor"]
):
# Fills should be a sibling to the building element
parent = ifcopenshell.util.element.get_aggregate(building_element)
if parent:
parent_obj = tool.Ifc.get_object(parent)
blenderbim.core.aggregate.assign_object(
tool.Ifc, tool.Aggregate, tool.Collector, relating_obj=parent_obj, related_obj=obj
)
else:
parent = ifcopenshell.util.element.get_container(building_element)
parent_obj = tool.Ifc.get_object(parent)
blenderbim.core.spatial.assign_container(
tool.Ifc, tool.Collector, tool.Spatial, structure_obj=parent_obj, element_obj=obj
)
# set occurences properties for the types defined with modifiers
if instance_class in ["IfcWindow", "IfcDoor"]:
pset_name = f"BBIM_{instance_class[3:]}"
@@ -179,7 +194,7 @@ class AddConstrTypeInstance(bpy.types.Operator):
if ifc_class == "IfcDoorType" and len(context.selected_objects) >= 1:
pass
else:
select_and_activate_single_object(context, obj)
tool.Blender.select_and_activate_single_object(context, obj)
return {"FINISHED"}
@staticmethod
@@ -277,8 +292,6 @@ class LoadTypeThumbnails(bpy.types.Operator, tool.Ifc.Operator):
offset: bpy.props.IntProperty()
def _execute(self, context):
from PIL import Image, ImageDraw
if bpy.app.background:
return
@@ -295,106 +308,11 @@ class LoadTypeThumbnails(bpy.types.Operator, tool.Ifc.Operator):
offset = 0
queue = queue[offset : offset + 9]
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
while queue:
# if bpy.app.is_job_running("RENDER_PREVIEW") does not seem to reflect asset preview generation
element = queue.pop()
obj = tool.Ifc.get_object(element)
if not obj:
continue # Nothing to process
elif AuthoringData.type_thumbnails.get(element.id(), None):
continue # Already processed
elif obj.preview and obj.preview.icon_id:
AuthoringData.type_thumbnails[element.id()] = obj.preview.icon_id
continue
if obj.data:
obj.asset_generate_preview()
while not obj.preview:
pass
else:
size = 128
img = Image.new("RGBA", (size, size))
draw = ImageDraw.Draw(img)
material = ifcopenshell.util.element.get_material(element)
if material and material.is_a("IfcMaterialProfileSet"):
profile = material.MaterialProfiles[0].Profile
tool.Profile.draw_image_for_ifc_profile(draw, profile, size)
elif material and material.is_a("IfcMaterialLayerSet"):
thicknesses = [l.LayerThickness for l in material.MaterialLayers]
total_thickness = sum(thicknesses)
si_total_thickness = total_thickness * unit_scale
if si_total_thickness <= 0.051:
width = 10
elif si_total_thickness <= 0.11:
width = 20
elif si_total_thickness <= 0.21:
width = 30
elif si_total_thickness <= 0.31:
width = 40
else:
width = 50
height = 100
is_horizontal = False
if element.is_a("IfcSlabType"):
is_horizontal = True
parametric = ifcopenshell.util.element.get_psets(element).get("EPset_Parametric")
if parametric:
layer_set_direction = parametric.get("LayerSetDirection", None)
if layer_set_direction == "AXIS2":
is_horizontal = False
elif layer_set_direction == "AXIS3":
is_horizontal = True
if is_horizontal:
width, height = height, width
x_offset = (size / 2) - (width / 2)
y_offset = (size / 2) - (height / 2)
draw.rectangle([x_offset, y_offset, width + x_offset, height + y_offset], outline="white", width=5)
current_thickness = 0
del thicknesses[-1]
for thickness in thicknesses:
current_thickness += thickness
if element.is_a("IfcSlabType"):
y = (current_thickness / total_thickness) * height
line = [x_offset, y_offset + y, x_offset + width, y_offset + y]
else:
x = (current_thickness / total_thickness) * width
line = [x_offset + x, y_offset, x_offset + x, y_offset + height]
draw.line(line, fill="white", width=2)
elif False:
# TODO: things like parametric duct segments
pass
elif not element.RepresentationMaps:
# Empties are represented by a generic thumbnail
width = height = 100
x_offset = (size / 2) - (width / 2)
y_offset = (size / 2) - (height / 2)
draw.line([x_offset, y_offset, width + x_offset, height + y_offset], fill="white", width=2)
draw.line([x_offset, y_offset + height, width + x_offset, y_offset], fill="white", width=2)
draw.rectangle([x_offset, y_offset, width + x_offset, height + y_offset], outline="white", width=5)
else:
draw.line([0, 0, size, size], fill="red", width=2)
draw.line([0, size, size, 0], fill="red", width=2)
pixels = [item for sublist in img.getdata() for item in sublist]
obj.asset_generate_preview()
while not obj.preview:
pass
obj.preview.image_size = size, size
obj.preview.image_pixels_float = pixels
queue.append(element)
if tool.Model.update_thumbnail_for_element(element):
queue.append(element)
return {"FINISHED"}
@@ -456,12 +374,6 @@ class MirrorElements(bpy.types.Operator, tool.Ifc.Operator):
obj.matrix_world = newmat
def copy_obj(self, obj):
new = obj.copy()
new.data = wall2.data.copy()
wall1.users_collection[0].objects.link(wall2)
blenderbim.core.root.copy_class(tool.Ifc, tool.Collector, tool.Geometry, tool.Root, obj=wall2)
def generate_box(usecase_path, ifc_file, settings):
box_context = ifcopenshell.util.representation.get_context(ifc_file, "Model", "Box", "MODEL_VIEW")
@@ -28,6 +28,7 @@ import blenderbim.bim.handler
import blenderbim.tool as tool
import blenderbim.core.type
import blenderbim.core.geometry
import blenderbim.core.material
from math import pi, degrees, inf
from mathutils import Vector, Matrix, Quaternion
from blenderbim.bim.module.geometry.helper import Helper
@@ -53,8 +54,8 @@ class DumbProfileGenerator:
self.axis_context = ifcopenshell.util.representation.get_context(tool.Ifc.get(), "Model", "Axis", "GRAPH_VIEW")
props = bpy.context.scene.BIMModelProperties
self.collection = bpy.context.view_layer.active_layer_collection.collection
self.collection_obj = bpy.data.objects.get(self.collection.name)
self.depth = props.extrusion_depth * self.unit_scale
self.collection_obj = self.collection.BIMCollectionProperties.obj
self.depth = props.extrusion_depth
self.rotation = 0
self.location = Vector((0, 0, 0))
self.cardinal_point = int(bpy.context.scene.BIMModelProperties.cardinal_point)
@@ -144,12 +145,21 @@ class DumbProfileRegenerator:
objs = []
if not profile:
return
element_types = set()
for element in self.get_elements_using_profile(profile):
obj = tool.Ifc.get_object(element)
if obj:
objs.append(obj)
if element.is_a("IfcElementType"):
element_types.add(element)
DumbProfileRecalculator().recalculate(objs)
# update related thumbnails
for element in self.get_element_types_using_profile(profile):
tool.Model.update_thumbnail_for_element(element, refresh=True)
def regenerate_from_profile(self, usecase_path, ifc_file, settings):
self.file = ifc_file
objs = []
@@ -164,9 +174,10 @@ class DumbProfileRegenerator:
def get_elements_using_profile(self, profile):
results = []
for profile_set in [
profile_sets = [
mp.ToMaterialProfileSet[0] for mp in self.file.get_inverse(profile) if mp.is_a("IfcMaterialProfile")
]:
]
for profile_set in profile_sets:
for inverse in self.file.get_inverse(profile_set):
if not inverse.is_a("IfcMaterialProfileSetUsage"):
continue
@@ -180,6 +191,18 @@ class DumbProfileRegenerator:
results.extend(rel.RelatedObjects)
return results
def get_element_types_using_profile(self, profile):
results = []
profile_sets = [
mp.ToMaterialProfileSet[0] for mp in self.file.get_inverse(profile) if mp.is_a("IfcMaterialProfile")
]
for profile_set in profile_sets:
for inverse in self.file.get_inverse(profile_set):
if not inverse.is_a("IfcRelAssociatesMaterial"):
continue
results.extend(inverse.RelatedObjects)
return results
def regenerate_from_type(self, usecase_path, ifc_file, settings):
obj = tool.Ifc.get_object(settings["related_object"])
if not obj or not obj.data or not obj.data.BIMMeshProperties.ifc_definition_id:
@@ -857,6 +880,25 @@ class Rotate90(bpy.types.Operator, tool.Ifc.Operator):
return {"FINISHED"}
class PatchNonParametricMepSegment(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.patch_non_parametric_mep_segment"
bl_label = "Set MEP segment Material Profile"
bl_options = {"REGISTER", "UNDO"}
@classmethod
def poll(cls, context):
return context.active_object
def _execute(self, context):
styles = tool.Geometry.get_styles(context.active_object)
blenderbim.core.material.patch_non_parametric_mep_segment(
tool.Ifc, tool.Material, tool.Profile, obj=context.active_object
)
bpy.ops.bim.enable_editing_extrusion_axis()
bpy.ops.bim.edit_extrusion_axis()
styles = tool.Geometry.get_styles(context.active_object)
class EnableEditingExtrusionAxis(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.enable_editing_extrusion_axis"
bl_label = "Enable Editing Extrusion Axis"
@@ -927,7 +969,7 @@ class DisableEditingExtrusionAxis(bpy.types.Operator, tool.Ifc.Operator):
return context.selected_objects
def _execute(self, context):
return disable_editing_extrusion_axis()
return disable_editing_extrusion_axis(context)
class EditExtrusionAxis(bpy.types.Operator, tool.Ifc.Operator):
@@ -948,6 +990,10 @@ class EditExtrusionAxis(bpy.types.Operator, tool.Ifc.Operator):
depth = (end - start).length
z_axis = (end - start).normalized()
y_axis = Vector((0, 0, 1))
# making sure z_axis != y_axis
if z_axis == y_axis:
y_axis = Vector((0,1,0))
x_axis = y_axis.cross(z_axis).normalized()
y_axis = z_axis.cross(x_axis).normalized()
+178 -113
View File
@@ -37,6 +37,12 @@ def get_type_class(self, context):
return AuthoringData.data["type_class"]
def get_boundary_class(self, context):
if not AuthoringData.is_loaded:
AuthoringData.load()
return AuthoringData.data["boundary_class"]
def get_relating_type_id(self, context):
if not AuthoringData.is_loaded:
AuthoringData.load()
@@ -51,7 +57,7 @@ def get_type_predefined_type(self, context):
def update_ifc_class(self, context):
bpy.ops.bim.load_type_thumbnails(ifc_class=self.ifc_class)
AuthoringData.data["relating_type_id"] = AuthoringData.relating_types()
AuthoringData.data["relating_type_id"] = AuthoringData.relating_type_id()
AuthoringData.data["type_thumbnail"] = AuthoringData.type_thumbnail()
@@ -63,6 +69,10 @@ def update_type_class(self, context):
AuthoringData.data["paginated_relating_types"] = AuthoringData.paginated_relating_types()
AuthoringData.data["type_predefined_type"] = AuthoringData.type_predefined_type()
type_class = self.type_class
if (type_class, type_class, "") in get_ifc_class(self, context):
self.ifc_class = type_class
def update_relating_type_id(self, context):
AuthoringData.data["relating_type_id"] = AuthoringData.relating_type_id()
@@ -76,7 +86,7 @@ def update_type_page(self, context):
class BIMModelProperties(PropertyGroup):
ifc_class: bpy.props.EnumProperty(items=get_ifc_class, name="Construction Class", update=update_ifc_class)
relating_type_id: bpy.props.EnumProperty(
items=get_relating_type_id, name="Construction Type", update=update_relating_type_id
items=get_relating_type_id, name="Relating Type", update=update_relating_type_id
)
icon_id: bpy.props.IntProperty()
updating: bpy.props.BoolProperty(default=False)
@@ -120,7 +130,7 @@ class BIMModelProperties(PropertyGroup):
z: bpy.props.FloatProperty(name="Z", default=0.5)
rl1: bpy.props.FloatProperty(name="RL", default=1) # Used for things like walls, doors, flooring, skirting, etc
rl2: bpy.props.FloatProperty(name="RL", default=1) # Used for things like windows, other hosted furniture
x_angle: bpy.props.FloatProperty(name="X Angle", default=0)
x_angle: bpy.props.FloatProperty(name="X Angle", default=0, subtype="ANGLE")
type_page: bpy.props.IntProperty(name="Type Page", default=1, update=update_type_page)
type_template: bpy.props.EnumProperty(
items=(
@@ -141,33 +151,7 @@ class BIMModelProperties(PropertyGroup):
type_class: bpy.props.EnumProperty(items=get_type_class, name="IFC Class", update=update_type_class)
type_predefined_type: bpy.props.EnumProperty(items=get_type_predefined_type, name="Predefined Type", default=None)
type_name: bpy.props.StringProperty(name="Name", default="TYPEX")
decorator_color_selected: bpy.props.FloatVectorProperty(
name="Selected Elements Color",
subtype="COLOR",
default=(0.545, 0.863, 0, 1), # green
min=0.0,
max=1.0,
size=4,
description="Color of selected verts/edges (used in profile editing mode)",
)
decorator_color_unselected: bpy.props.FloatVectorProperty(
name="Not Selected Elements Color",
subtype="COLOR",
default=(1, 1, 1, 1), # green
min=0.0,
max=1.0,
size=4,
description="Color of not selected verts/edges (used in profile editing mode)",
)
decorator_color_special: bpy.props.FloatVectorProperty(
name="Special Elements Color",
subtype="COLOR",
default=(0.157, 0.565, 1, 1), # blue
min=0.0,
max=1.0,
size=4,
description="Color of special selected verts/edges (openings, preview verts/edges in roof editing, verts with arcs/circles in profile editing)",
)
boundary_class: bpy.props.EnumProperty(items=get_boundary_class, name="Boundary Class")
class BIMArrayProperties(PropertyGroup):
@@ -175,9 +159,9 @@ class BIMArrayProperties(PropertyGroup):
default=-1, description="Currently edited array index. -1 if not in array editing mode."
)
count: bpy.props.IntProperty(name="Count", default=0, min=0)
x: bpy.props.FloatProperty(name="X", default=0)
y: bpy.props.FloatProperty(name="Y", default=0)
z: bpy.props.FloatProperty(name="Z", default=0)
x: bpy.props.FloatProperty(name="X", default=0, subtype="DISTANCE")
y: bpy.props.FloatProperty(name="Y", default=0, subtype="DISTANCE")
z: bpy.props.FloatProperty(name="Z", default=0, subtype="DISTANCE")
use_local_space: bpy.props.BoolProperty(
name="Use Local Space",
description="Use local space for array items offset instead of world space",
@@ -196,25 +180,25 @@ class BIMArrayProperties(PropertyGroup):
class BIMStairProperties(PropertyGroup):
non_si_units_props = ("is_editing", "number_of_treads", "has_top_nib", "stair_type")
stair_types = (
("CONCRETE", "Concrete", ""),
("WOOD/STEEL", "Wood / Steel", ""),
("GENERIC", "Generic", ""),
)
stair_added_previously: bpy.props.BoolProperty(default=False)
is_editing: bpy.props.IntProperty(default=-1)
width: bpy.props.FloatProperty(name="Width", default=1.2, soft_min=0.01)
height: bpy.props.FloatProperty(name="Height", default=1.0, soft_min=0.01)
width: bpy.props.FloatProperty(name="Width", default=1.2, soft_min=0.01, subtype="DISTANCE")
height: bpy.props.FloatProperty(name="Height", default=1.0, soft_min=0.01, subtype="DISTANCE")
number_of_treads: bpy.props.IntProperty(name="Number of treads", default=6, soft_min=1)
tread_depth: bpy.props.FloatProperty(name="Tread Depth", default=0.25, soft_min=0.01)
tread_run: bpy.props.FloatProperty(name="Tread Run", default=0.3, soft_min=0.01)
base_slab_depth: bpy.props.FloatProperty(name="Base slab depth", default=0.25, soft_min=0)
top_slab_depth: bpy.props.FloatProperty(name="Top slab depth", default=0.25, soft_min=0)
tread_depth: bpy.props.FloatProperty(name="Tread Depth", default=0.25, soft_min=0.01, subtype="DISTANCE")
tread_run: bpy.props.FloatProperty(name="Tread Run", default=0.3, soft_min=0.01, subtype="DISTANCE")
base_slab_depth: bpy.props.FloatProperty(name="Base slab depth", default=0.25, soft_min=0, subtype="DISTANCE")
top_slab_depth: bpy.props.FloatProperty(name="Top slab depth", default=0.25, soft_min=0, subtype="DISTANCE")
has_top_nib: bpy.props.BoolProperty(name="Has top nib", default=True)
stair_type: bpy.props.EnumProperty(name="Stair type", items=stair_types, default="CONCRETE")
def get_props_kwargs(self):
def get_props_kwargs(self, convert_to_project_units=False):
stair_kwargs = {
"stair_type": self.stair_type,
"width": self.width,
@@ -224,27 +208,34 @@ class BIMStairProperties(PropertyGroup):
}
if self.stair_type == "CONCRETE":
stair_kwargs.update(
{
"base_slab_depth": self.base_slab_depth,
"top_slab_depth": self.top_slab_depth,
"has_top_nib": self.has_top_nib,
"tread_depth": self.tread_depth,
}
)
return stair_kwargs
concrete_props = {
"base_slab_depth": self.base_slab_depth,
"top_slab_depth": self.top_slab_depth,
"has_top_nib": self.has_top_nib,
"tread_depth": self.tread_depth,
}
stair_kwargs.update(concrete_props)
elif self.stair_type == "WOOD/STEEL":
stair_kwargs.update(
{
"tread_depth": self.tread_depth,
}
)
return stair_kwargs
wood_steel_props = {
"tread_depth": self.tread_depth,
}
stair_kwargs.update(wood_steel_props)
elif self.stair_type == "GENERIC":
pass
if not convert_to_project_units:
return stair_kwargs
stair_kwargs = tool.Model.convert_data_to_project_units(stair_kwargs, self.non_si_units_props)
return stair_kwargs
def set_props_kwargs_from_ifc_data(self, kwargs):
kwargs = tool.Model.convert_data_to_si_units(kwargs, self.non_si_units_props)
for prop_name in kwargs:
setattr(self, prop_name, kwargs[prop_name])
class BIMSverchokProperties(PropertyGroup):
node_group: bpy.props.PointerProperty(name="Node Group", type=NodeTree)
@@ -252,15 +243,13 @@ class BIMSverchokProperties(PropertyGroup):
def window_type_prop_update(self, context):
number_of_panels, panels_data = self.window_types_panels[self.window_type]
si_coversion = 1.0 / ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
panels_data = [[v * si_coversion for v in data] for data in panels_data]
self.first_mullion_offset, self.second_mullion_offset = panels_data[0]
self.first_transom_offset, self.second_transom_offset = panels_data[1]
# default prop values are in mm and converted later
class BIMWindowProperties(PropertyGroup):
non_si_units_props = ("is_editing", "window_type")
window_types = (
("SINGLE_PANEL", "SINGLE_PANEL", ""),
("DOUBLE_PANEL_HORIZONTAL", "DOUBLE_PANEL_HORIZONTAL", ""),
@@ -288,55 +277,67 @@ class BIMWindowProperties(PropertyGroup):
}
# fmt: on
window_added_previously: bpy.props.BoolProperty(default=False)
is_editing: bpy.props.IntProperty(default=-1)
window_type: bpy.props.EnumProperty(
name="Window Type", items=window_types, default="SINGLE_PANEL", update=window_type_prop_update
)
overall_height: bpy.props.FloatProperty(name="Overall Height", default=0.9)
overall_width: bpy.props.FloatProperty(name="Overall Width", default=0.6)
overall_height: bpy.props.FloatProperty(name="Overall Height", default=0.9, subtype="DISTANCE")
overall_width: bpy.props.FloatProperty(name="Overall Width", default=0.6, subtype="DISTANCE")
# lining properties
lining_depth: bpy.props.FloatProperty(name="Lining Depth", default=0.050)
lining_thickness: bpy.props.FloatProperty(name="Lining Thickness", default=0.050)
lining_offset: bpy.props.FloatProperty(name="Lining Offset", default=0.050)
lining_to_panel_offset_x: bpy.props.FloatProperty(name="Lining to Panel Offset X", default=0.025)
lining_to_panel_offset_y: bpy.props.FloatProperty(name="Lining to Panel Offset Y", default=0.025)
mullion_thickness: bpy.props.FloatProperty(name="Mullion Thickness", default=0.050)
lining_depth: bpy.props.FloatProperty(name="Lining Depth", default=0.050, subtype="DISTANCE")
lining_thickness: bpy.props.FloatProperty(name="Lining Thickness", default=0.050, subtype="DISTANCE")
lining_offset: bpy.props.FloatProperty(name="Lining Offset", default=0.050, subtype="DISTANCE")
lining_to_panel_offset_x: bpy.props.FloatProperty(
name="Lining to Panel Offset X", default=0.025, subtype="DISTANCE"
)
lining_to_panel_offset_y: bpy.props.FloatProperty(
name="Lining to Panel Offset Y", default=0.025, subtype="DISTANCE"
)
mullion_thickness: bpy.props.FloatProperty(name="Mullion Thickness", default=0.050, subtype="DISTANCE")
first_mullion_offset: bpy.props.FloatProperty(
name="First Mullion Offset",
description="Distance from the first lining to the first mullion center",
default=0.3,
subtype="DISTANCE",
)
second_mullion_offset: bpy.props.FloatProperty(
name="Second Mullion Offset",
description="Distance from the first lining to the second mullion center",
default=0.45,
subtype="DISTANCE",
)
transom_thickness: bpy.props.FloatProperty(name="Transom Thickness", default=0.050)
transom_thickness: bpy.props.FloatProperty(name="Transom Thickness", default=0.050, subtype="DISTANCE")
first_transom_offset: bpy.props.FloatProperty(
name="First Transom Offset",
description="Distance from the first lining to the first transom center",
default=0.3,
subtype="DISTANCE",
)
second_transom_offset: bpy.props.FloatProperty(
name="Second Transom Offset",
description="Distance from the first lining to the second transom center",
default=0.6,
subtype="DISTANCE",
)
# panel properties
frame_depth: bpy.props.FloatVectorProperty(name="Frame Depth", size=3, default=[0.035] * 3)
frame_thickness: bpy.props.FloatVectorProperty(name="Frame Thickness", size=3, default=[0.035] * 3)
frame_depth: bpy.props.FloatVectorProperty(name="Frame Depth", size=3, default=[0.035] * 3, subtype="TRANSLATION")
frame_thickness: bpy.props.FloatVectorProperty(
name="Frame Thickness", size=3, default=[0.035] * 3, subtype="TRANSLATION"
)
def get_general_kwargs(self):
return {
def get_general_kwargs(self, convert_to_project_units=False):
kwargs = {
"window_type": self.window_type,
"overall_height": self.overall_height,
"overall_width": self.overall_width,
}
if not convert_to_project_units:
return kwargs
return tool.Model.convert_data_to_project_units(kwargs, ["window_type"])
def get_lining_kwargs(self):
def get_lining_kwargs(self, convert_to_project_units=False):
kwargs = {
"lining_depth": self.lining_depth,
"lining_thickness": self.lining_thickness,
@@ -373,16 +374,27 @@ class BIMWindowProperties(PropertyGroup):
if self.window_type in ("TRIPLE_PANEL_HORIZONTAL",):
kwargs["second_transom_offset"] = self.second_transom_offset
return kwargs
if not convert_to_project_units:
return kwargs
return tool.Model.convert_data_to_project_units(kwargs)
def get_panel_kwargs(self):
return {
def get_panel_kwargs(self, convert_to_project_units=False):
kwargs = {
"frame_depth": self.frame_depth,
"frame_thickness": self.frame_thickness,
}
if not convert_to_project_units:
return kwargs
return tool.Model.convert_data_to_project_units(kwargs)
def set_props_kwargs_from_ifc_data(self, kwargs):
kwargs = tool.Model.convert_data_to_si_units(kwargs, self.non_si_units_props)
for prop_name in kwargs:
setattr(self, prop_name, kwargs[prop_name])
class BIMDoorProperties(PropertyGroup):
non_si_units_props = ("is_editing", "door_type", "panel_width_ratio")
door_types = (
("SINGLE_SWING_LEFT", "SINGLE_SWING_LEFT", ""),
("SINGLE_SWING_RIGHT", "SINGLE_SWING_RIGHT", ""),
@@ -394,51 +406,56 @@ class BIMDoorProperties(PropertyGroup):
("DOUBLE_DOOR_SLIDING", "DOUBLE_DOOR_SLIDING", ""),
)
door_added_previously: bpy.props.BoolProperty(default=False)
is_editing: bpy.props.IntProperty(default=-1)
door_type: bpy.props.EnumProperty(name="Door Operation Type", items=door_types, default="SINGLE_SWING_LEFT")
overall_height: bpy.props.FloatProperty(name="Overall Height", default=2.0)
overall_width: bpy.props.FloatProperty(name="Overall Width", default=0.9)
overall_height: bpy.props.FloatProperty(name="Overall Height", default=2.0, subtype="DISTANCE")
overall_width: bpy.props.FloatProperty(name="Overall Width", default=0.9, subtype="DISTANCE")
# lining properties
lining_depth: bpy.props.FloatProperty(name="Lining Depth", default=0.050)
lining_thickness: bpy.props.FloatProperty(name="Lining Thickness", default=0.050)
lining_depth: bpy.props.FloatProperty(name="Lining Depth", default=0.050, subtype="DISTANCE")
lining_thickness: bpy.props.FloatProperty(name="Lining Thickness", default=0.050, subtype="DISTANCE")
lining_offset: bpy.props.FloatProperty(
name="Lining Offset",
description="Offset from the outer side of the wall (by Y-axis). "
"If present then adding casing is not possible.\n"
"`0.025 mm` is good as default value",
default=0.0,
default=0.0,
subtype="DISTANCE"
)
lining_to_panel_offset_x: bpy.props.FloatProperty(name="Lining to Panel Offset X", default=0.025)
lining_to_panel_offset_y: bpy.props.FloatProperty(name="Lining to Panel Offset Y", default=0.025)
lining_to_panel_offset_x: bpy.props.FloatProperty(name="Lining to Panel Offset X", default=0.025, subtype="DISTANCE")
lining_to_panel_offset_y: bpy.props.FloatProperty(name="Lining to Panel Offset Y", default=0.025, subtype="DISTANCE")
transom_thickness: bpy.props.FloatProperty(
name="Transom Thickness",
description="Set values > 0 to add a transom.\n" "`0.050 mm` is good as default value",
default=0.000,
subtype="DISTANCE"
)
transom_offset: bpy.props.FloatProperty(
name="Transom Offset",
description="Distance from the bottom door opening to the beginning of the transom (unlike windows)",
default=1.525,
subtype="DISTANCE"
)
casing_thickness: bpy.props.FloatProperty(
name="Casing Thickness", description="Set values > 0 and LiningOffset = 0 to add a casing.", default=0.075
name="Casing Thickness", description="Set values > 0 and LiningOffset = 0 to add a casing.", default=0.075,
subtype="DISTANCE"
)
casing_depth: bpy.props.FloatProperty(name="Casing Depth", default=0.005)
casing_depth: bpy.props.FloatProperty(name="Casing Depth", default=0.005, subtype="DISTANCE")
threshold_thickness: bpy.props.FloatProperty(
name="Threshold Thickness", description="Set values > 0 to add a threshold.", default=0.025
name="Threshold Thickness", description="Set values > 0 to add a threshold.", default=0.025,
subtype="DISTANCE"
)
threshold_depth: bpy.props.FloatProperty(name="Threshold Depth", default=0.1)
threshold_depth: bpy.props.FloatProperty(name="Threshold Depth", default=0.1, subtype="DISTANCE")
threshold_offset: bpy.props.FloatProperty(
name="Threshold Offset", description="`0.025 mm` is good as default value", default=0.000
name="Threshold Offset", description="`0.025 mm` is good as default value", default=0.000,
subtype="DISTANCE"
)
# panel properties
panel_depth: bpy.props.FloatProperty(name="Panel Depth", default=0.035)
panel_depth: bpy.props.FloatProperty(name="Panel Depth", default=0.035, subtype="DISTANCE")
panel_width_ratio: bpy.props.FloatProperty(
name="Panel Width Ratio",
description="Width of this panel, given as ratio " "relative to the total clear opening width of the door",
@@ -446,17 +463,20 @@ class BIMDoorProperties(PropertyGroup):
soft_min=0,
soft_max=1,
)
frame_thickness: bpy.props.FloatProperty(name="Window Frame Thickness", default=0.035)
frame_depth: bpy.props.FloatProperty(name="Window Frame Depth", default=0.035)
frame_thickness: bpy.props.FloatProperty(name="Window Frame Thickness", default=0.035, subtype="DISTANCE")
frame_depth: bpy.props.FloatProperty(name="Window Frame Depth", default=0.035, subtype="DISTANCE")
def get_general_kwargs(self):
return {
def get_general_kwargs(self, convert_to_project_units=False):
kwargs = {
"door_type": self.door_type,
"overall_height": self.overall_height,
"overall_width": self.overall_width,
}
if not convert_to_project_units:
return kwargs
return tool.Model.convert_data_to_project_units(kwargs, ["door_type"])
def get_lining_kwargs(self):
def get_lining_kwargs(self, convert_to_project_units=False):
kwargs = {
"lining_depth": self.lining_depth,
"lining_thickness": self.lining_thickness,
@@ -481,40 +501,56 @@ class BIMDoorProperties(PropertyGroup):
kwargs["threshold_depth"] = self.threshold_depth
kwargs["threshold_offset"] = self.threshold_offset
return kwargs
if not convert_to_project_units:
return kwargs
return tool.Model.convert_data_to_project_units(kwargs)
def get_panel_kwargs(self):
def get_panel_kwargs(self, convert_to_project_units=False):
kwargs = {"panel_depth": self.panel_depth, "panel_width_ratio": self.panel_width_ratio}
if self.transom_thickness:
kwargs["frame_thickness"] = self.frame_thickness
kwargs["frame_depth"] = self.frame_depth
return kwargs
if not convert_to_project_units:
return kwargs
return tool.Model.convert_data_to_project_units(kwargs, ("panel_width_ratio", ))
def set_props_kwargs_from_ifc_data(self, kwargs):
kwargs = tool.Model.convert_data_to_si_units(kwargs, self.non_si_units_props)
for prop_name in kwargs:
setattr(self, prop_name, kwargs[prop_name])
class BIMRailingProperties(PropertyGroup):
non_si_units_props = (
"is_editing",
"railing_type",
"use_manual_supports",
"terminal_type",
"path_data",
)
railing_types = (
("FRAMELESS_PANEL", "FRAMELESS_PANEL", ""),
("WALL_MOUNTED_HANDRAIL", "WALL_MOUNTED_HANDRAIL", ""),
)
cap_types = (
("none", "none", ""),
("TO_END_POST_AND_FLOOR", "TO_END_POST_AND_FLOOR", ""),
("TO_END_POST", "TO_END_POST", ""),
("TO_FLOOR", "TO_FLOOR", ""),
("TO_WALL", "TO_WALL", ""),
("180", "180", ""),
("to_wall", "to_wall", ""),
("to_floor", "to_floor", ""),
("to_end_post", "to_end_post", ""),
("to_end_post_and_floor", "to_end_post_and_floor", ""),
("NONE", "NONE", ""),
)
railing_added_previously: bpy.props.BoolProperty(default=False)
is_editing: bpy.props.IntProperty(default=-1)
is_editing_path: bpy.props.BoolProperty(default=False)
railing_type: bpy.props.EnumProperty(name="Railing Type", items=railing_types, default="FRAMELESS_PANEL")
height: bpy.props.FloatProperty(name="Height", default=1.0)
thickness: bpy.props.FloatProperty(name="Thickness", default=0.050)
spacing: bpy.props.FloatProperty(name="Spacing", default=0.050)
height: bpy.props.FloatProperty(name="Height", default=1.0, subtype="DISTANCE")
thickness: bpy.props.FloatProperty(name="Thickness", default=0.050, subtype="DISTANCE")
spacing: bpy.props.FloatProperty(name="Spacing", default=0.050, subtype="DISTANCE")
# wall mounted handrail specific properties
use_manual_supports: bpy.props.BoolProperty(
@@ -532,7 +568,7 @@ class BIMRailingProperties(PropertyGroup):
)
terminal_type: bpy.props.EnumProperty(name="Terminal Type", items=cap_types, default="180")
def get_general_kwargs(self):
def get_general_kwargs(self, convert_to_project_units=False):
base_kwargs = {
"railing_type": self.railing_type,
"height": self.height,
@@ -551,17 +587,34 @@ class BIMRailingProperties(PropertyGroup):
"support_spacing": self.support_spacing,
"terminal_type": self.terminal_type,
}
return base_kwargs | additional_kwargs
kwargs = base_kwargs | additional_kwargs
if not convert_to_project_units:
return kwargs
kwargs = tool.Model.convert_data_to_project_units(kwargs, self.non_si_units_props)
return kwargs
def set_props_kwargs_from_ifc_data(self, kwargs):
kwargs = tool.Model.convert_data_to_si_units(kwargs, self.non_si_units_props)
for prop_name in kwargs:
setattr(self, prop_name, kwargs[prop_name])
class BIMRoofProperties(PropertyGroup):
non_si_units_props = (
"is_editing",
"path_data",
"roof_type",
"generation_method",
"angle",
"rafter_edge_angle",
)
roof_types = (("HIP/GABLE ROOF", "HIP/GABLE ROOF", ""),)
roof_generation_methods = (
("HEIGHT", "HEIGHT", ""),
("ANGLE", "ANGLE", ""),
)
roof_added_previously: bpy.props.BoolProperty(default=False)
is_editing: bpy.props.IntProperty(default=-1)
is_editing_path: bpy.props.BoolProperty(default=False)
@@ -573,14 +626,26 @@ class BIMRoofProperties(PropertyGroup):
name="Height", default=1.0, description="Maximum height of the roof to be generated.", subtype="DISTANCE"
)
angle: bpy.props.FloatProperty(name="Slope Angle", default=pi / 18, subtype="ANGLE")
roof_thickness: bpy.props.FloatProperty(name="Roof Thickness", default=0.1, subtype="DISTANCE")
rafter_edge_angle: bpy.props.FloatProperty(name="Rafter Edge Angle", min=0, max=pi, default=pi / 2, subtype="ANGLE")
def get_general_kwargs(self):
def get_general_kwargs(self, convert_to_project_units=False):
kwargs = {
"roof_type": self.roof_type,
"generation_method": self.generation_method,
"roof_thickness": self.roof_thickness,
"rafter_edge_angle": self.rafter_edge_angle,
}
if self.generation_method == "HEIGHT":
kwargs["height"] = self.height
else:
kwargs["angle"] = self.angle
return kwargs
if not convert_to_project_units:
return kwargs
return tool.Model.convert_data_to_project_units(kwargs, self.non_si_units_props)
def set_props_kwargs_from_ifc_data(self, kwargs):
kwargs = tool.Model.convert_data_to_si_units(kwargs, self.non_si_units_props)
for prop_name in kwargs:
setattr(self, prop_name, kwargs[prop_name])
@@ -38,14 +38,6 @@ import json
# https://ifc43-docs.standards.buildingsmart.org/IFC/RELEASE/IFC4x3/HTML/lexical/IfcRailingType.htm
NON_SI_RAILING_PROPS = (
"is_editing",
"railing_type",
"railing_added_previously",
"use_manual_supports",
"terminal_type",
)
def bm_split_edge_at_offset(edge, offset):
v0, v1 = edge.verts
@@ -92,16 +84,18 @@ def update_railing_modifier_ifc_data(context):
if props.railing_type == "WALL_MOUNTED_HANDRAIL":
body = ifcopenshell.util.representation.get_context(ifc_file, "Model", "Body", "MODEL_VIEW")
railing_path = [Vector(v) for v in RailingData.data["parameters"]["data_dict"]["path_data"]["verts"]]
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
representation_data = {
"railing_type": props.railing_type,
"context": body,
"railing_path": railing_path,
"use_manual_supports": props.use_manual_supports,
"support_spacing": props.support_spacing,
"railing_diameter": props.railing_diameter,
"clear_width": props.clear_width,
"support_spacing": props.support_spacing / si_conversion,
"railing_diameter": props.railing_diameter / si_conversion,
"clear_width": props.clear_width / si_conversion,
"terminal_type": props.terminal_type,
"height": props.height,
"height": props.height / si_conversion,
}
model_representation = ifcopenshell.api.run(
"geometry.add_railing_representation", ifc_file, **representation_data
@@ -153,9 +147,9 @@ def update_railing_modifier_bmesh(context):
def generate_frameless_panel_railing():
# generating FRAMELESS_PANEL railing
height = props.height * si_conversion
thickness = props.thickness * si_conversion
spacing = props.spacing * si_conversion
height = props.height
thickness = props.thickness
spacing = props.spacing
# spacing
# split each edge in 3 segments by 0.5 * spacing by x-y plane
@@ -279,6 +273,9 @@ class BIM_OT_add_railing(bpy.types.Operator, tool.Ifc.Operator):
mesh = bpy.data.meshes.new("IfcRailing")
obj = bpy.data.objects.new("IfcRailing", mesh)
obj.location = spawn_location
collection = context.view_layer.active_layer_collection.collection
collection.objects.link(obj)
body_context = ifcopenshell.util.representation.get_context(ifc_file, "Model", "Body", "MODEL_VIEW")
blenderbim.core.root.assign_class(
tool.Ifc,
@@ -313,11 +310,8 @@ class AddRailing(bpy.types.Operator, tool.Ifc.Operator):
self.report({"ERROR"}, "Object has to be IfcRailing/IfcRailingType type to add a railing.")
return {"CANCELLED"}
# need to make sure all default props will have correct units
if not props.railing_added_previously:
convert_property_group_from_si(props, skip_props=NON_SI_RAILING_PROPS)
railing_data = props.get_general_kwargs()
railing_data = props.get_general_kwargs(convert_to_project_units=True)
path_data = get_path_data(obj)
if not path_data:
path_data = {
@@ -350,13 +344,7 @@ class EnableEditingRailing(bpy.types.Operator, tool.Ifc.Operator):
data["path_data"] = json.dumps(data["path_data"])
# required since we could load pset from .ifc and BIMRailingProperties won't be set
for prop_name in data:
setattr(props, prop_name, data[prop_name])
# need to make sure all props that weren't used before
# will have correct units
skip_props = NON_SI_RAILING_PROPS + tuple(data.keys())
convert_property_group_from_si(props, skip_props=skip_props)
props.set_props_kwargs_from_ifc_data(data)
props.is_editing = 1
return {"FINISHED"}
@@ -373,8 +361,7 @@ class CancelEditingRailing(bpy.types.Operator, tool.Ifc.Operator):
data = json.loads(ifcopenshell.util.element.get_pset(element, "BBIM_Railing", "Data"))
props = obj.BIMRailingProperties
# restore previous settings since editing was canceled
for prop_name in data:
setattr(props, prop_name, data[prop_name])
props.set_props_kwargs_from_ifc_data(data)
body = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
blenderbim.core.geometry.switch_representation(
@@ -405,7 +392,7 @@ class FinishEditingRailing(bpy.types.Operator, tool.Ifc.Operator):
RailingData.load()
path_data = RailingData.data["parameters"]["data_dict"]["path_data"]
railing_data = props.get_general_kwargs()
railing_data = props.get_general_kwargs(convert_to_project_units=True)
railing_data["path_data"] = path_data
props.is_editing = -1
@@ -437,7 +424,7 @@ class FlipRailingPathOrder(bpy.types.Operator, tool.Ifc.Operator):
edge = [abs(vi - last_vert_i) for vi in edge[::-1]]
edges.append(edge)
railing_data = props.get_general_kwargs()
railing_data = props.get_general_kwargs(convert_to_project_units=True)
railing_data["path_data"] = path_data
update_bbim_railing_pset(element, railing_data)
@@ -496,7 +483,7 @@ class FinishEditingRailingPath(bpy.types.Operator, tool.Ifc.Operator):
element = tool.Ifc.get_entity(obj)
props = obj.BIMRailingProperties
railing_data = props.get_general_kwargs()
railing_data = props.get_general_kwargs(convert_to_project_units=True)
path_data = get_path_data(obj)
railing_data["path_data"] = path_data
ProfileDecorator.uninstall()
@@ -524,7 +511,6 @@ class RemoveRailing(bpy.types.Operator, tool.Ifc.Operator):
pset = tool.Pset.get_element_pset(element, "BBIM_Railing")
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), pset=pset)
props.railing_added_previously = True
return {"FINISHED"}
@@ -28,8 +28,9 @@ from blenderbim.bim.module.model.data import RoofData, refresh
from blenderbim.bim.module.model.decorator import ProfileDecorator
import json
from math import tan, pi
from math import tan, pi, radians
from mathutils import Vector, Matrix
import mathutils.geometry
from bpypolyskel import bpypolyskel
import shapely
from pprint import pprint
@@ -39,9 +40,6 @@ from pprint import pprint
# https://ifc43-docs.standards.buildingsmart.org/IFC/RELEASE/IFC4x3/HTML/lexical/IfcRoofType.htm
NON_SI_ROOF_PROPS = ("is_editing", "roof_type", "roof_added_previously", "generation_method", "angle")
def float_is_zero(f):
return 0.0001 >= f >= -0.0001
@@ -87,12 +85,32 @@ class GenerateHippedRoof(bpy.types.Operator, tool.Ifc.Operator):
return {"CANCELLED"}
bm = tool.Blender.get_bmesh_for_mesh(obj.data)
op_status, error_message = is_valid_roof_footprint(bm)
if error_message:
self.report(op_status, error_message)
return {"CANCELLED"}
generate_hiped_roof_bmesh(bm, self.mode, self.height, self.angle)
tool.Blender.apply_bmesh(obj.data, bm)
return {"FINISHED"}
def generate_hiped_roof_bmesh(bm, mode="ANGLE", height=1.0, angle=pi / 18, mutate_current_bmesh=True):
def is_valid_roof_footprint(bm):
# should be bmesh to support edit mode
bm.verts.ensure_lookup_table()
base_z = bm.verts[0].co.z
all_verts_same_level = all([float_is_zero(v.co.z - base_z) for v in bm.verts[1:]])
if not all_verts_same_level:
return (
{"ERROR"},
"\nAll roof footprint vertices should have same Z-level.\nCurrently Z-level doesn't completely match",
)
return ({"FINISHED"}, "")
def generate_hiped_roof_bmesh(
bm, mode="ANGLE", height=1.0, roof_thickness=0.1, angle=pi / 18, rafter_edge_angle=pi / 2, mutate_current_bmesh=True
):
"""return bmesh with gable roof geometry
`mutate_current_bmesh` is a flag to indicate whether the input bmesh
@@ -215,9 +233,15 @@ def generate_hiped_roof_bmesh(bm, mode="ANGLE", height=1.0, angle=pi / 18, mutat
verts_to_rip = []
bottom_chords_to_remove = []
def is_footprint_vert(v):
return float_is_zero(v.co.z - footprint_z)
def is_footprint_edge(edge):
return all(is_footprint_vert(v) for v in edge.verts)
# find footprint edges
for edge in bm.edges:
if all(float_is_zero(v.co.z - footprint_z) for v in edge.verts):
if is_footprint_edge(edge):
footprint_edges.append(edge)
footprint_verts.update(edge.verts)
@@ -229,27 +253,29 @@ def generate_hiped_roof_bmesh(bm, mode="ANGLE", height=1.0, angle=pi / 18, mutat
# iterate over edges from original geometry
# if their angle was redefined by user - apply the changes to the related vertices
# to match the requested angle
process_later = []
for old_edge_verts, defined_angle, separate_verts in original_geometry_data["edges"]:
if not defined_angle:
continue
edge_verts_remaped = set(old_verts_remap[old_vert] for old_vert in old_edge_verts)
identical_edge = None
for edge in footprint_edges:
if set(edge.verts) == edge_verts_remaped:
identical_edge = edge
break
assert identical_edge
verts_to_move = find_other_polygon_verts(identical_edge)
for v in verts_to_move:
if not separate_verts:
vert_co = verts_to_change.get(v, v.co)
new_vert_co = change_angle(vert_co, edge_verts_remaped, defined_angle)
verts_to_change[v] = new_vert_co
else:
if separate_verts:
verts_to_move = find_other_polygon_verts(identical_edge)
for v in verts_to_move:
vert_co = v.co
new_vert_co = change_angle(vert_co, edge_verts_remaped, defined_angle)
verts_to_rip.append([v, new_vert_co, identical_edge])
else:
process_later.append([identical_edge, edge_verts_remaped, defined_angle])
if defined_angle >= pi / 2:
bottom_chords_to_remove.append(identical_edge)
@@ -264,20 +290,91 @@ def generate_hiped_roof_bmesh(bm, mode="ANGLE", height=1.0, angle=pi / 18, mutat
if cur_edge == edge:
continue
bmesh.ops.contextual_create(bm, geom=[new_edge, cur_edge])
return new_v
# correct angle for verts that require verts separation
# store separated verts for angle correction later
related_verts = {}
for v, new_co, edge in verts_to_rip:
new_v = separate_vert(bm, v, edge, new_co)
related_verts.setdefault(v, []).append(new_v)
# verts angle correction.
# we're taking into account new verts created after verts separation
# required for asymmetrical gable roof
for identical_edge, edge_verts_remaped, defined_angle in process_later:
verts_to_move = find_other_polygon_verts(identical_edge)
for v in verts_to_move[:]:
verts_to_move.extend(related_verts.get(v, []))
for v in verts_to_move:
vert_co = verts_to_change.get(v, v.co)
new_vert_co = change_angle(vert_co, edge_verts_remaped, defined_angle)
verts_to_change[v] = new_vert_co
# apply all changes once at the end
for v in verts_to_change:
v.co = verts_to_change[v]
for v, new_co, edge in verts_to_rip:
separate_vert(bm, v, edge, new_co)
bmesh.ops.delete(bm, geom=bottom_chords_to_remove, context="EDGES")
extrusion_geom = bmesh.ops.extrude_face_region(bm, geom=bm.faces)["geom"]
extruded_verts = bm_sort_out_geom(extrusion_geom)["verts"]
bmesh.ops.translate(bm, vec=[0.0, 0.0, 0.1], verts=extruded_verts)
# add roof thickness
extrusion_geom = bm_sort_out_geom(bmesh.ops.extrude_face_region(bm, geom=bm.faces)["geom"])
extruded_edges = extrusion_geom["edges"]
extruded_verts = extrusion_geom["verts"]
rafter_edge_angle = pi / 2 - rafter_edge_angle
default_offset_dir = Vector([0, 0, 1]) * roof_thickness
footprint_verts = set()
if not float_is_zero(rafter_edge_angle):
footprint_edges = []
for edge in extruded_edges:
if is_footprint_edge(edge):
footprint_edges.append(edge)
footprint_verts.update(edge.verts)
def get_top_vert(v):
non_footprint_verts = []
for edge in v.link_edges:
v1 = edge.other_vert(v)
if not is_footprint_vert(v1):
non_footprint_verts.append(v1)
if len(non_footprint_verts) == 1:
return non_footprint_verts[0]
return min(non_footprint_verts, key=lambda v1: (v1.co - v.co).length)
top_verts = dict()
for v in footprint_verts:
top_verts[v] = get_top_vert(v)
# TODO: rafter_edge_angle might differ
# if footprint edges are not connected by 90 degrees
verts_offsets = dict()
for edge in footprint_edges:
v0, v1 = edge.verts
edge_dir = (v0.co - v1.co).normalized()
offset_dir = Matrix.Rotation(rafter_edge_angle, 4, edge_dir) @ default_offset_dir
for v in edge.verts:
previous_offset = verts_offsets.get(v, None)
if previous_offset:
previous_offset.xy += offset_dir.xy
else:
verts_offsets[v] = offset_dir
for v in verts_offsets:
vert_offset = verts_offsets[v]
top = top_verts[v].co
bot = v.co + default_offset_dir
base = v.co
offsetted = v.co + vert_offset
# all this math is needed to maintain the same roof thickness
# for different rafter edge angles
v.co = mathutils.geometry.intersect_line_line(bot, top, base, offsetted)[0]
verts = [v for v in extruded_verts if v not in footprint_verts]
bmesh.ops.translate(bm, vec=default_offset_dir, verts=verts)
bmesh.ops.recalc_face_normals(bm, faces=bm.faces[:])
return bm
@@ -357,8 +454,15 @@ def update_roof_modifier_bmesh(context):
tool.Blender.apply_bmesh(obj.data, bm)
return
height = props.height * si_conversion
generate_hiped_roof_bmesh(bm, props.generation_method, height, props.angle, mutate_current_bmesh=True)
generate_hiped_roof_bmesh(
bm,
props.generation_method,
props.height,
props.roof_thickness,
props.angle,
props.rafter_edge_angle,
mutate_current_bmesh=True,
)
tool.Blender.apply_bmesh(obj.data, bm)
@@ -410,6 +514,9 @@ class BIM_OT_add_roof(bpy.types.Operator, tool.Ifc.Operator):
mesh = bpy.data.meshes.new("IfcRoof")
obj = bpy.data.objects.new("IfcRoof", mesh)
obj.location = spawn_location
collection = context.view_layer.active_layer_collection.collection
collection.objects.link(obj)
body_context = ifcopenshell.util.representation.get_context(ifc_file, "Model", "Body", "MODEL_VIEW")
blenderbim.core.root.assign_class(
tool.Ifc,
@@ -444,22 +551,30 @@ class AddRoof(bpy.types.Operator, tool.Ifc.Operator):
self.report({"ERROR"}, "Object has to be IfcRoof/IfcRoofType type to add a roof.")
return {"CANCELLED"}
# need to make sure all default props will have correct units
if not props.roof_added_previously:
convert_property_group_from_si(props, skip_props=NON_SI_ROOF_PROPS)
# rejecting original roof shape to be safe
# taking into account only it's bounding box dimensions
if obj.dimensions.x == 0 or obj.dimensions.y == 0:
min_x, min_y = -5, -5
max_x, max_y = 5, 5
min_z = 0
else:
bbox = tool.Blender.get_object_bounding_box(obj)
min_x = bbox["min_x"]
min_y = bbox["min_y"]
max_x = bbox["max_x"]
max_y = bbox["max_y"]
min_z = bbox["min_z"]
roof_data = props.get_general_kwargs()
path_data = get_path_data(obj)
if not path_data:
path_data = {
"edges": [[0, 1], [1, 2], [2, 3], [3, 0]],
"verts": [
Vector([-5.0, -5.0, 0.0]) / si_conversion,
Vector([-5.0, 5.0, 0.0]) / si_conversion,
Vector([5.0, 5.0, 0.0]) / si_conversion,
Vector([5.0, -5.0, 0.0]) / si_conversion,
],
}
roof_data = props.get_general_kwargs(convert_to_project_units=True)
path_data = {
"edges": [[0, 1], [1, 2], [2, 3], [3, 0]],
"verts": [
Vector([min_x, min_y, min_z]) / si_conversion,
Vector([min_x, max_y, min_z]) / si_conversion,
Vector([max_x, max_y, min_z]) / si_conversion,
Vector([max_x, min_y, min_z]) / si_conversion,
],
}
roof_data["path_data"] = path_data
update_bbim_roof_pset(element, roof_data)
@@ -479,17 +594,8 @@ class EnableEditingRoof(bpy.types.Operator, tool.Ifc.Operator):
props = obj.BIMRoofProperties
element = tool.Ifc.get_entity(obj)
data = json.loads(ifcopenshell.util.element.get_pset(element, "BBIM_Roof", "Data"))
data["path_data"] = json.dumps(data["path_data"])
# required since we could load pset from .ifc and BIMRoofProperties won't be set
for prop_name in data:
setattr(props, prop_name, data[prop_name])
# need to make sure all props that weren't used before
# will have correct units
skip_props = NON_SI_ROOF_PROPS + tuple(data.keys())
convert_property_group_from_si(props, skip_props=skip_props)
props.set_props_kwargs_from_ifc_data(data)
props.is_editing = 1
return {"FINISHED"}
@@ -505,8 +611,7 @@ class CancelEditingRoof(bpy.types.Operator, tool.Ifc.Operator):
data = json.loads(ifcopenshell.util.element.get_pset(element, "BBIM_Roof", "Data"))
props = obj.BIMRoofProperties
# restore previous settings since editing was canceled
for prop_name in data:
setattr(props, prop_name, data[prop_name])
props.set_props_kwargs_from_ifc_data(data)
body = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
blenderbim.core.geometry.switch_representation(
@@ -537,7 +642,7 @@ class FinishEditingRoof(bpy.types.Operator, tool.Ifc.Operator):
RoofData.load()
path_data = RoofData.data["parameters"]["data_dict"]["path_data"]
roof_data = props.get_general_kwargs()
roof_data = props.get_general_kwargs(convert_to_project_units=True)
roof_data["path_data"] = path_data
props.is_editing = -1
@@ -572,12 +677,21 @@ class EnableEditingRoofPath(bpy.types.Operator, tool.Ifc.Operator):
# copying to make sure not to mutate the edit mode bmesh
bm = tool.Blender.get_bmesh_for_mesh(obj.data)
main_bm = bm.copy()
op_status, error_message = is_valid_roof_footprint(main_bm)
if error_message:
print("Error: %s" % error_message)
return main_bm
main_bm.edges.layers.int.new("BBIM_preview")
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
height = props.height * si_conversion
second_bm = generate_hiped_roof_bmesh(
bm, props.generation_method, height, props.angle, mutate_current_bmesh=False
bm,
props.generation_method,
props.height,
props.roof_thickness,
props.angle,
props.rafter_edge_angle,
mutate_current_bmesh=False,
)
bmesh.ops.translate(second_bm, verts=second_bm.verts, vec=Vector((0, 0, 1)))
@@ -625,7 +739,13 @@ class FinishEditingRoofPath(bpy.types.Operator, tool.Ifc.Operator):
element = tool.Ifc.get_entity(obj)
props = obj.BIMRoofProperties
roof_data = props.get_general_kwargs()
bm = tool.Blender.get_bmesh_for_mesh(obj.data)
op_status, error_message = is_valid_roof_footprint(bm)
if error_message:
self.report(op_status, error_message)
return {"CANCELLED"}
roof_data = props.get_general_kwargs(convert_to_project_units=True)
path_data = get_path_data(obj)
roof_data["path_data"] = path_data
ProfileDecorator.uninstall()
@@ -634,6 +754,7 @@ class FinishEditingRoofPath(bpy.types.Operator, tool.Ifc.Operator):
update_bbim_roof_pset(element, roof_data)
refresh() # RoofData has to be updated before run update_roof_modifier_bmesh
update_roof_modifier_bmesh(context)
update_roof_modifier_ifc_data(context)
if bpy.context.object.mode == "EDIT":
bpy.ops.object.mode_set(mode="OBJECT")
@@ -654,7 +775,6 @@ class RemoveRoof(bpy.types.Operator, tool.Ifc.Operator):
pset = tool.Pset.get_element_pset(element, "BBIM_Roof")
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), pset=pset)
props.roof_added_previously = True
return {"FINISHED"}
@@ -43,7 +43,7 @@ def sync_name(usecase_path, ifc_file, settings):
style = tool.Style.get_style(obj)
if style and style != element:
style.Name = new_name
collection = bpy.data.collections.get(obj.name)
collection = obj.BIMObjectProperties.collection
if collection:
collection.name = new_name
obj.name = new_name
@@ -133,13 +133,13 @@ class DumbSlabGenerator:
props = bpy.context.scene.BIMModelProperties
self.collection = bpy.context.view_layer.active_layer_collection.collection
self.collection_obj = bpy.data.objects.get(self.collection.name)
self.collection_obj = self.collection.BIMCollectionProperties.obj
self.depth = sum(thicknesses) * unit_scale
self.width = 3
self.length = 3
self.rotation = 0
self.location = Vector((0, 0, 0))
self.x_angle = 0 if tool.Cad.is_x(props.x_angle, 0, tolerance=0.001) else radians(props.x_angle)
self.x_angle = 0 if tool.Cad.is_x(props.x_angle, 0, tolerance=0.001) else props.x_angle
return self.derive_from_cursor()
def derive_from_cursor(self):
@@ -252,9 +252,17 @@ class DumbSlabPlaner:
new_material = ifcopenshell.util.element.get_material(settings["relating_type"])
if not new_material or not new_material.is_a("IfcMaterialLayerSet"):
return
parametric = ifcopenshell.util.element.get_psets(settings["relating_type"]).get("EPset_Parametric")
layer_set_direction = None
if parametric:
layer_set_direction = parametric.get("LayerSetDirection", layer_set_direction)
new_thickness = sum([l.LayerThickness for l in new_material.MaterialLayers])
material = ifcopenshell.util.element.get_material(settings["related_object"])
if material and material.is_a("IfcMaterialLayerSetUsage") and material.LayerSetDirection == "AXIS3":
if not material or not material.is_a("IfcMaterialLayerSetUsage"):
return
if layer_set_direction:
material.LayerSetDirection = layer_set_direction
if material.LayerSetDirection == "AXIS3":
self.change_thickness(settings["related_object"], new_thickness)
def change_thickness(self, element, thickness):
@@ -274,7 +282,7 @@ class DumbSlabPlaner:
extrusion.Depth = thickness
else:
props = bpy.context.scene.BIMModelProperties
x_angle = 0 if tool.Cad.is_x(props.x_angle, 0, tolerance=0.001) else radians(props.x_angle)
x_angle = 0 if tool.Cad.is_x(props.x_angle, 0, tolerance=0.001) else props.x_angle
new_rep = ifcopenshell.api.run(
"geometry.add_slab_representation",
tool.Ifc.get(),
@@ -299,7 +307,7 @@ class DumbSlabPlaner:
return
else:
props = bpy.context.scene.BIMModelProperties
x_angle = 0 if tool.Cad.is_x(props.x_angle, 0, tolerance=0.001) else radians(props.x_angle)
x_angle = 0 if tool.Cad.is_x(props.x_angle, 0, tolerance=0.001) else props.x_angle
representation = ifcopenshell.api.run(
"geometry.add_slab_representation",
tool.Ifc.get(),
@@ -26,12 +26,20 @@ import blenderbim.tool as tool
import blenderbim.core.type
from math import pi
from mathutils import Vector, Matrix
from shapely import Polygon, MultiPolygon
class GenerateSpace(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.generate_space"
bl_label = "Generate Space"
bl_options = {"REGISTER"}
bl_description = "Create a space from the cursor position. Move the cursor position into the desired position, select the right space collection and run the operator"
@classmethod
def poll(cls, context):
collection = context.view_layer.active_layer_collection.collection
collection_obj = collection.BIMCollectionProperties.obj
return tool.Ifc.get_entity(collection_obj)
def _execute(self, context):
# This only works based on a 2D plan only considering the standard
@@ -52,7 +60,7 @@ class GenerateSpace(bpy.types.Operator, tool.Ifc.Operator):
relating_type = None
collection = context.view_layer.active_layer_collection.collection
collection_obj = bpy.data.objects.get(collection.name)
collection_obj = collection.BIMCollectionProperties.obj
if not collection_obj:
bpy.context.window_manager.popup_menu(msg, title="Error", icon="ERROR")
return
@@ -65,8 +73,13 @@ class GenerateSpace(bpy.types.Operator, tool.Ifc.Operator):
element = None
if bpy.context.selected_objects and active_obj:
element = tool.Ifc.get_entity(active_obj)
x, y, z = active_obj.matrix_world.translation.xyz
mat = active_obj.matrix_world
local_bbox_center = 0.125 * sum((Vector(b) for b in active_obj.bound_box), Vector())
global_bbox_center = mat @ local_bbox_center
x = global_bbox_center.x
y = global_bbox_center.y
z = (mat @ Vector(active_obj.bound_box[0])).z
h = active_obj.dimensions.z
else:
x, y = context.scene.cursor.location.xy
@@ -166,3 +179,225 @@ class GenerateSpace(bpy.types.Operator, tool.Ifc.Operator):
(obj.matrix_world @ Vector((max_x, max_y, 0.0))).to_2d(),
],
}
class GenerateSpacesFromWalls(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.generate_spaces_from_walls"
bl_label = "Generate Spaces From Walls"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Generate spaces from selected walls. The active object must be a wall"
@classmethod
def poll(cls, context):
active_obj = bpy.context.active_object
element = tool.Ifc.get_entity(active_obj)
if element:
return context.selected_objects and element.is_a("IfcWall")
def _execute(self, context):
# This only works based on a 2D plan only considering the standard
# walls (i.e. prismatic) in the active object storey.
# In order to run, the active objct must be a wall and
# have to be selected walls
props = context.scene.BIMModelProperties
active_obj = bpy.context.active_object
if not active_obj:
self.report({"ERROR"}, "No active object. Please select a wall")
return
element = tool.Ifc.get_entity(active_obj)
if element and not element.is_a("IfcWall"):
return self.report({"ERROR"}, "The active object is not a wall. Please select a wall.")
container = ifcopenshell.util.element.get_container(element)
if not container:
self.report({"ERROR"}, "The wall is not contained.")
if not bpy.context.selected_objects:
self.report({"ERROR"}, "No selected objects found. Please select walls.")
return
x, y, z = active_obj.matrix_world.translation.xyz
mat = active_obj.matrix_world
h = active_obj.dimensions.z
selected_objects = bpy.context.selected_objects
boundary_elements = self.get_boundary_elements(selected_objects)
polys = self.get_polygons(boundary_elements)
converted_tolerance = self.get_converted_tolerance(tolerance=0.03)
union = shapely.ops.unary_union(polys).buffer(converted_tolerance, cap_style=2, join_style=2)
union = self.get_purged_inner_holes_poly(union_geom = union, min_area = self.get_converted_tolerance(tolerance = 3))
for i, linear_ring in enumerate(union.interiors):
poly = Polygon(linear_ring)
poly = poly.buffer(converted_tolerance, single_sided=True, cap_style=2, join_style=2)
bm = self.get_bmesh_from_polygon(poly, mat, h)
name = "Space" + str(i)
mesh = bpy.data.meshes.new(name=name)
bm.to_mesh(mesh)
bm.free()
obj = bpy.data.objects.new(name, mesh)
obj.matrix_world = mat
self.set_obj_origin_to_bboxcenter(obj)
if z != 0:
obj.location = obj.location + Vector((0,0,z))
context.view_layer.active_layer_collection.collection.objects.link(obj)
bpy.ops.bim.assign_class(obj=obj.name, ifc_class="IfcSpace")
container_obj = tool.Ifc.get_object(container)
blenderbim.core.spatial.assign_container(
tool.Ifc, tool.Collector, tool.Spatial, structure_obj=container_obj, element_obj=obj
)
def get_boundary_elements(self, selected_objects):
boundary_elements = []
for obj in selected_objects:
subelement = tool.Ifc.get_entity(obj)
if subelement.is_a("IfcWall") or subelement.is_a("IfcColumn"):
boundary_elements.append(subelement)
return boundary_elements
def get_polygons(self, boundary_elements):
polys = []
for boundary_element in boundary_elements:
obj = tool.Ifc.get_object(boundary_element)
if not obj:
continue
points = []
base = self.get_obj_base_points(obj)
for index in ["low_left", "low_right", "high_right", "high_left"]:
point = base[index]
points.append(point)
polys.append(Polygon(points))
return polys
def get_obj_base_points(self, obj):
x_values = [(obj.matrix_world @ Vector(v)).x for v in obj.bound_box]
y_values = [(obj.matrix_world @ Vector(v)).y for v in obj.bound_box]
return {
"low_left": (x_values[0], y_values[0]),
"high_left": (x_values[3], y_values[3]),
"low_right": (x_values[4], y_values[4]),
"high_right": (x_values[7], y_values[7]),
}
def get_converted_tolerance(self, tolerance):
model = tool.Ifc.get()
project_unit = ifcopenshell.util.unit.get_project_unit(model, "LENGTHUNIT")
prefix = getattr(project_unit, "Prefix", None)
converted_tolerance = ifcopenshell.util.unit.convert(
value=tolerance,
from_prefix=None,
from_unit="METRE",
to_prefix=prefix,
to_unit=project_unit.Name,
)
return tolerance
def get_purged_inner_holes_poly(self, union_geom, min_area):
interiors_list = []
if union_geom.geom_type == "MultiPolygon":
for poly in union_geom.geoms:
interiors_list = self.get_poly_valid_interior_list(poly = poly, min_area = min_area, interiors_list = interiors_list)
new_poly = Polygon(poly.exterior.coords, holes = interiors_list)
if union_geom.geom_type == "Polygon":
interiors_list = self.get_poly_valid_interior_list(poly = union_geom, min_area = min_area, interiors_list = interiors_list)
new_poly = Polygon(union_geom.exterior.coords, holes = interiors_list)
return new_poly
def get_poly_valid_interior_list(self, poly, min_area, interiors_list):
for interior in poly.interiors:
p = Polygon(interior)
if p.area >= min_area:
interiors_list.append(interior)
return interiors_list
def get_bmesh_from_polygon(self, poly, mat, h):
bm = bmesh.new()
bm.verts.index_update()
bm.edges.index_update()
mat_invert = mat.inverted()
new_verts = [bm.verts.new(mat_invert @ Vector([v[0], v[1], 0])) for v in poly.exterior.coords[0:-1]]
[bm.edges.new((new_verts[i], new_verts[i + 1])) for i in range(len(new_verts) - 1)]
bm.edges.new((new_verts[len(new_verts) - 1], new_verts[0]))
bm.verts.index_update()
bm.edges.index_update()
bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=1e-5)
bmesh.ops.triangle_fill(bm, edges=bm.edges)
bmesh.ops.dissolve_limit(bm, angle_limit=pi / 180 * 5, verts=bm.verts, edges=bm.edges)
extrusion = bmesh.ops.extrude_face_region(bm, geom=bm.faces)
extruded_verts = [g for g in extrusion["geom"] if isinstance(g, bmesh.types.BMVert)]
bmesh.ops.translate(bm, vec=[0.0, 0.0, h], verts=extruded_verts)
bmesh.ops.recalc_face_normals(bm, faces=bm.faces)
return bm
def set_obj_origin_to_bboxcenter(self, obj):
mat = obj.matrix_world
inverted = mat.inverted()
local_bbox_center = 0.125 * sum((Vector(b) for b in obj.bound_box), Vector())
global_bbox_center = mat @ local_bbox_center
oldLoc = obj.location
newLoc = global_bbox_center
diff = newLoc - oldLoc
for vert in obj.data.vertices:
aux_vector = mat @ vert.co
aux_vector = aux_vector - diff
vert.co = inverted @ aux_vector
obj.location = newLoc
class ToggleSpaceVisibility(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.toggle_space_visibility"
bl_label = "Toggle Space Visibility"
bl_options = {"REGISTER"}
bl_description = "Change the space visibility"
def execute(cls, context):
model = tool.Ifc.get()
spaces = model.by_type('IfcSpace')
if not spaces:
print(spaces)
return {"FINISHED"}
first_obj = tool.Ifc.get_object(spaces[0])
if bpy.data.objects[first_obj.name].display_type == 'TEXTURED':
for space in spaces:
obj = tool.Ifc.get_object(space)
bpy.data.objects[obj.name].show_wire = True
bpy.data.objects[obj.name].display_type = 'WIRE'
return {"FINISHED"}
elif bpy.data.objects[first_obj.name].display_type == 'WIRE':
for space in spaces:
obj = tool.Ifc.get_object(space)
bpy.data.objects[obj.name].show_wire = False
bpy.data.objects[obj.name].display_type = 'TEXTURED'
return {"FINISHED"}

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