Compare commits
27 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c79eb60959 | |||
| 538419a23f | |||
| eb19165ebc | |||
| 1138ffee7e | |||
| 0f3416774c | |||
| 1ba7977587 | |||
| 5b1b8e8f50 | |||
| 78811d331b | |||
| 4aa91cfa7f | |||
| c099a6cb75 | |||
| 029b402d11 | |||
| a243cdd6c6 | |||
| 1fae835de9 | |||
| f0c7ff1e57 | |||
| 460fd5ca87 | |||
| 054c0a9a27 | |||
| 5af1baa48b | |||
| 0e5d2cc0c9 | |||
| 48148f00a3 | |||
| 564c4b7644 | |||
| 33c53a2c9a | |||
| c049007ee8 | |||
| 05345e8ddf | |||
| 461b78665a | |||
| 6160d65d18 | |||
| d526314190 | |||
| 85bac37bed |
@@ -18,14 +18,14 @@ jobs:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
lfs: true
|
||||
submodules: recursive
|
||||
-
|
||||
name: Update ubuntu
|
||||
run: |
|
||||
sudo apt update
|
||||
sudo apt-get install git cmake gcc g++ libboost1.67-all-dev python3-all-dev swig libpcre3-dev libxml2-dev
|
||||
sudo apt-get install libocct-foundation-dev libocct-modeling-algorithms-dev libocct-modeling-data-dev libocct-ocaf-dev libocct-visualization-dev libocct-data-exchange-dev \
|
||||
libhdf5-dev
|
||||
libhdf5-dev libcgal-dev
|
||||
|
||||
- name: Build
|
||||
env:
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
ISO-10303-21;
|
||||
HEADER;
|
||||
FILE_DESCRIPTION((),'2;1');
|
||||
FILE_NAME('EPset_Drawing.ifc','2020-01-01T00:00:00',(),(),'EPset_Drawing','EPset_Drawing',$);
|
||||
FILE_SCHEMA(('IFC4'));
|
||||
ENDSEC;
|
||||
DATA;
|
||||
#1=IFCPROPERTYSETTEMPLATE('2JhNIvqZrFnAgxfhK0XVQX',$,'EPset_Drawing','',.PSET_OCCURRENCEDRIVEN.,'IfcAnnotation',(#2,#3,#4,#5,#6,#7));
|
||||
#2=IFCSIMPLEPROPERTYTEMPLATE('23JavTMk98ZxXhrUEnjAcf',$,'TargetView','',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
#3=IFCSIMPLEPROPERTYTEMPLATE('1yVWUt5H9DAOuu0OaMMLpe',$,'Scale','',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
#4=IFCSIMPLEPROPERTYTEMPLATE('3gsuPBtU93b8f0gg1pjkq6',$,'HumanScale','',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.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.);
|
||||
ENDSEC;
|
||||
END-ISO-10303-21;
|
||||
@@ -23,10 +23,22 @@ text { fill: black; stroke: none; }
|
||||
.cut { fill: black; stroke: black; stroke-linecap: 'round'; stroke-width: 0.35; fill-rule: evenodd; }
|
||||
.projection { fill: white; stroke: black; stroke-linecap: 'round'; stroke-width: 0.25; }
|
||||
.annotation { fill: none; stroke: black; stroke-linecap: 'round'; stroke-width: 0.25; }
|
||||
.hidden { stroke-dasharray: 3, 2; }
|
||||
.solid {}
|
||||
.leader { marker-end: url(#leader-marker); }
|
||||
.stair { marker-start: url(#stair-marker-start); marker-end: url(#stair-marker-end); }
|
||||
.IfcAnnotation { fill: none; stroke: black; stroke-linecap: 'round'; stroke-width: 0.25; }
|
||||
.PredefinedType-MISC { stroke: black; stroke-width: 0.25; }
|
||||
.PredefinedType-BACKGROUND { stroke: black; stroke-width: 0.18; }
|
||||
.PredefinedType-GRID { marker-start: url(#grid-marker); marker-end: url(#grid-marker); }
|
||||
.PredefinedType-SECTIONLEVEL { marker-start: url(#section-level-marker); stroke-dasharray: 12.5, 3, 3, 3; }
|
||||
.PredefinedType-PLANLEVEL { marker-end: url(#plan-level-marker); }
|
||||
.PredefinedType-DIMENSION { marker-start: url(#dimension-marker-start); marker-end: url(#dimension-marker-end); }
|
||||
.PredefinedType-HIDDENLINE { stroke-dasharray: 3, 2; }
|
||||
.PredefinedType-STAIR { marker-start: url(#stair-marker-start); marker-end: url(#stair-marker-end); }
|
||||
.PredefinedType-BOUNDARY { fill: none; stroke: red; stroke-width: 1; stroke-dasharray: 12, 4, 3, 4, 3, 4; }
|
||||
.PredefinedType-BATTING { marker-start: url(#batting); stroke: none; }
|
||||
.PredefinedType-SEALANT { fill: url(#crosshatch1); stroke-width: 0.25; }
|
||||
.PredefinedType-TEXT { fill: black; stroke: none; }
|
||||
path.PredefinedType-TEXTLEADER { marker-end: url(#leader-marker); }
|
||||
text.PredefinedType-TEXTLEADER { fill: black; stroke: none; }
|
||||
.break { fill: white; }
|
||||
.breakline { fill: none; stroke: black; stroke-linecap: 'round'; stroke-width: 0.25; marker-mid: url(#breakline-marker); }
|
||||
.material-blank { fill: white; }
|
||||
@@ -44,5 +56,9 @@ text { fill: black; stroke: none; }
|
||||
.material-honeycomb { fill: url(#honeycomb); }
|
||||
.material-sand { fill: url(#sand); }
|
||||
.material-concrete { fill: url(#concrete); stroke-width: 0.5; }
|
||||
.material-boundary { fill: none; stroke: red; stroke-width: 1; stroke-dasharray: 12,4,3,4,3,4; }
|
||||
.IfcSpace { fill: none; stroke: none; }
|
||||
.PredefinedType-STUD { stroke: black; stroke-width: 1; }
|
||||
.PredefinedType-WOOD { fill: url(#wood); stroke: black; stroke-width: 0.5; }
|
||||
.PredefinedType-STEEL { fill: url(#steel); stroke: black; stroke-width: 0.5; }
|
||||
.PredefinedType-CONCRETE { fill: url(#concrete); stroke: black; stroke-width: 0.5; }
|
||||
.PredefinedType-PLASTERBOARD { fill: url(#sand); stroke: black; stroke-width: 0.25; }
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<svg baseProfile="full" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:ev="http://www.w3.org/2001/xml-events" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<marker id="batting" markerHeight="10" markerWidth="4" orient="auto" refX="0" refY="5" markerUnits="userSpaceOnUse">
|
||||
<!-- Tip: https://svg-path-visualizer.netlify.app/ -->
|
||||
<path style="fill: none; stroke:black; stroke-width:0.18" d="M 0 2 A 2 2 0 0 1 4 2 L 2 8 M 0 2 L 2 8 A 2 2 0 0 0 4 10 M 2 8 A 2 2 0 0 1 0 10" />
|
||||
</marker>
|
||||
<marker id="eng-dimension-marker-start" markerHeight="14" markerWidth="11" orient="auto" refX="1" refY="7">
|
||||
<g>
|
||||
<path d="M 11 3.5 L 11 10.5 L 1 7" class="annotation" style="fill:black;" />
|
||||
|
||||
|
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 3.9 KiB |
@@ -3,6 +3,10 @@
|
||||
<pattern id="demolish" width="1" height="1" patternTransform="rotate(45 0 0)" patternUnits="userSpaceOnUse">
|
||||
<line x1="0" y1="0" x2="0" y2="1" style="stroke:red; stroke-width:0.25" />
|
||||
</pattern>
|
||||
<pattern id="steel" width="3" height="1" patternTransform="rotate(45 0 0)" patternUnits="userSpaceOnUse">
|
||||
<line x1="0" y1="0" x2="0" y2="1" style="stroke:black; stroke-width:0.25" />
|
||||
<line x1="1" y1="0" x2="1" y2="1" style="stroke:black; stroke-width:0.25" />
|
||||
</pattern>
|
||||
<pattern id="diagonal1" width="1" height="1" patternTransform="rotate(45 0 0)" patternUnits="userSpaceOnUse">
|
||||
<line x1="0" y1="0" x2="0" y2="1" style="stroke:black; stroke-width:0.25" />
|
||||
</pattern>
|
||||
@@ -80,6 +84,9 @@
|
||||
<path d="M 1.25,2.165 L 2.5,4.33 L 5,4.33 L 6.25,2.165 L 5,0 L 2.5,0 L 1.25,2.165" style="stroke:black;stroke-width:0.25;stroke-linecap:round;fill:none" />
|
||||
<path d="M 6.25,2.165 L 7.5,2.165" style="stroke:black;stroke-width:0.25;stroke-linecap:round" />
|
||||
</pattern>
|
||||
<pattern id="wood" width="50" height="50" patternUnits="userSpaceOnUse">
|
||||
<path style="fill:none; stroke: black; stroke-width:0.25;" d="M 49.649999,1.3460737 A 69.019995,69.019995 0 0 0 48.653938,0.34999949 M 49.649999,4.9389583 A 66.554994,66.554994 0 0 0 45.06105,0.34999949 M 49.649999,8.6983527 A 64.089999,64.089999 0 0 0 41.301656,0.34999949 M 49.649999,12.675003 A 61.624998,61.624998 0 0 0 37.325,0.34999949 M 49.649999,16.948067 A 59.159997,59.159997 0 0 0 33.05192,0.34999949 M 49.649999,21.652972 A 56.694997,56.694997 0 0 0 28.347017,0.34999949 M 49.649999,27.057906 A 54.229999,54.229999 0 0 0 22.942096,0.34999949 M 49.649999,33.866298 A 51.764998,51.764998 0 0 0 16.1337,0.34999949 M 49.649999,49.649989 A 49.299997,49.299997 0 0 0 0.34999949,0.34999949 M 47.185,49.649989 A 46.834999,46.834999 0 0 0 0.34999949,2.8149951 M 44.72,49.649989 A 44.369998,44.369998 0 0 0 0.34999949,5.2799906 M 42.254999,49.649989 A 41.904997,41.904997 0 0 0 0.34999949,7.7450123 M 39.790001,49.649989 A 39.439999,39.439999 0 0 0 0.34999949,10.210008 M 37.325,49.649989 A 36.974998,36.974998 0 0 0 0.34999949,12.675003 M 34.859999,49.649989 A 34.509998,34.509998 0 0 0 0.34999949,15.139998 M 32.395001,49.649989 A 32.044999,32.044999 0 0 0 0.34999949,17.604994 M 29.93,49.649989 A 29.579999,29.579999 0 0 0 0.34999949,20.069989 M 27.465,49.649989 A 27.114998,27.114998 0 0 0 0.34999949,22.535011 M 24.999997,49.649989 A 24.649999,24.649999 0 0 0 0.34999949,25.000007 M 22.534998,49.649989 A 22.184999,22.184999 0 0 0 0.34999949,27.465003 M 20.069998,49.649989 A 19.719999,19.719999 0 0 0 0.34999949,29.929999 M 17.604998,49.649989 A 17.254999,17.254999 0 0 0 0.34999949,32.394994 M 15.139998,49.649989 A 14.789999,14.789999 0 0 0 0.34999949,34.85999 M 12.674999,49.649989 A 12.325,12.325 0 0 0 0.34999949,37.325011 M 10.209999,49.649989 A 9.8599994,9.8599994 0 0 0 0.34999949,39.790007 M 7.7449994,49.649989 A 7.3949999,7.3949999 0 0 0 0.34999949,42.255002 M 5.2799992,49.649989 A 4.9299997,4.9299997 0 0 0 0.34999949,44.719998 M 2.8149995,49.649989 A 2.4649999,2.4649999 0 0 0 0.34999949,47.184993 M 0.34999949,49.649989 H 3.8010003 M 0.34999949,49.649989 H 3.8010003 M 0.34999949,49.649989 H 3.8010003" />
|
||||
</pattern>
|
||||
<pattern id="sand" width="50" height="50" patternUnits="userSpaceOnUse">
|
||||
<path style="fill: white;" d="M 0 0 50 0 50 50 0 50" />
|
||||
<path
|
||||
|
||||
|
Before Width: | Height: | Size: 65 KiB After Width: | Height: | Size: 67 KiB |
@@ -173,7 +173,7 @@
|
||||
style="stroke-width:0.999999px"
|
||||
y="1036.1166"
|
||||
x="3030.9653"
|
||||
id="tspan2527">{{revision}}</tspan></text>
|
||||
id="tspan2527">{{Revision}}</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:13.3918px;line-height:125%;font-family:'OpenGost Type B TT';-inkscape-font-specification:'OpenGost Type B TT';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.999999px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
@@ -183,7 +183,7 @@
|
||||
id="tspan2531"
|
||||
x="2804.7021"
|
||||
y="1036.1166"
|
||||
style="stroke-width:0.999999px">{{number}}</tspan></text>
|
||||
style="stroke-width:0.999999px">{{Identification}} - {{Name}}</tspan></text>
|
||||
</g>
|
||||
<g
|
||||
transform="matrix(0.26458333,0,0,0.26458334,-3.180917e-5,297.00003)"
|
||||
|
||||
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
@@ -169,7 +169,7 @@
|
||||
style="stroke-width:0.264583px"
|
||||
y="397.13919"
|
||||
x="554.94287"
|
||||
id="tspan2527">{{revision}}</tspan></text>
|
||||
id="tspan2527">{{Revision}}</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:3.54325px;line-height:125%;font-family:'OpenGost Type B TT';-inkscape-font-specification:'OpenGost Type B TT';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
@@ -179,7 +179,7 @@
|
||||
id="tspan2531"
|
||||
x="495.07742"
|
||||
y="397.13919"
|
||||
style="stroke-width:0.264583px">{{number}}</tspan></text>
|
||||
style="stroke-width:0.264583px">{{Identification}} - {{Name}}</tspan></text>
|
||||
<g
|
||||
style="stroke-width:3.77953"
|
||||
id="g90669"
|
||||
|
||||
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
@@ -172,7 +172,7 @@
|
||||
id="tspan2527"
|
||||
x="544.94287"
|
||||
y="284.13919"
|
||||
style="stroke-width:0.264583px">{{revision}}</tspan></text>
|
||||
style="stroke-width:0.264583px">{{Revision}}</tspan></text>
|
||||
<text
|
||||
id="text2533"
|
||||
y="284.13919"
|
||||
@@ -182,7 +182,7 @@
|
||||
style="stroke-width:0.264583px"
|
||||
y="284.13919"
|
||||
x="485.07742"
|
||||
id="tspan2531">{{number}}</tspan></text>
|
||||
id="tspan2531">{{Identification}} - {{Name}}</tspan></text>
|
||||
</g>
|
||||
<g
|
||||
style="stroke-width:3.77953"
|
||||
|
||||
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
@@ -70,7 +70,7 @@
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:4.96055365px;font-family:'OpenGost Type B TT';-inkscape-font-specification:'OpenGost Type B TT';text-align:center;text-anchor:middle;stroke-width:0.26458332px"
|
||||
y="103.10933"
|
||||
x="32.822662"
|
||||
id="tspan857">{{no}}</tspan></text>
|
||||
id="tspan857">{{Identification}}</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:17.71626091px;line-height:125%;font-family:Arial;-inkscape-font-specification:Arial;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458329px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
@@ -80,7 +80,7 @@
|
||||
id="tspan861"
|
||||
x="39.12896"
|
||||
y="100.19173"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:3.54325247px;font-family:'OpenGost Type B TT';-inkscape-font-specification:'OpenGost Type B TT';text-align:start;text-anchor:start;stroke-width:0.26458329px">{{name}}</tspan></text>
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:3.54325247px;font-family:'OpenGost Type B TT';-inkscape-font-specification:'OpenGost Type B TT';text-align:start;text-anchor:start;stroke-width:0.26458329px">{{Name}}</tspan></text>
|
||||
<text
|
||||
id="text867"
|
||||
y="105.0553"
|
||||
@@ -90,6 +90,6 @@
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:3.54325247px;font-family:'OpenGost Type B TT';-inkscape-font-specification:'OpenGost Type B TT';text-align:start;text-anchor:start;stroke-width:0.26458332px"
|
||||
y="105.0553"
|
||||
x="39.285267"
|
||||
id="tspan865">{{scale}}</tspan></text>
|
||||
id="tspan865">{{Scale}}</tspan></text>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 4.4 KiB |
@@ -157,12 +157,12 @@ def loadIfcStore(scene):
|
||||
if not IfcStore.get_file():
|
||||
return
|
||||
IfcStore.get_schema()
|
||||
IfcStore.reload_linked_elements()
|
||||
IfcStore.relink_all_objects()
|
||||
|
||||
|
||||
@persistent
|
||||
def undo_pre(scene):
|
||||
IfcStore.update_undo_redo_stack_objects()
|
||||
IfcStore.track_undo_redo_stack_object_map()
|
||||
|
||||
|
||||
@persistent
|
||||
@@ -171,13 +171,13 @@ def undo_post(scene):
|
||||
IfcStore.last_transaction = bpy.context.scene.BIMProperties.last_transaction
|
||||
IfcStore.undo()
|
||||
purge_module_data()
|
||||
IfcStore.update_undo_redo_stack_objects()
|
||||
IfcStore.reload_linked_elements(objects=[bpy.data.objects.get(o) for o in IfcStore.undo_redo_stack_objects])
|
||||
IfcStore.track_undo_redo_stack_selected_objects()
|
||||
IfcStore.reload_undo_redo_stack_objects()
|
||||
|
||||
|
||||
@persistent
|
||||
def redo_pre(scene):
|
||||
IfcStore.update_undo_redo_stack_objects()
|
||||
IfcStore.track_undo_redo_stack_object_map()
|
||||
|
||||
|
||||
@persistent
|
||||
@@ -186,8 +186,8 @@ def redo_post(scene):
|
||||
IfcStore.last_transaction = bpy.context.scene.BIMProperties.last_transaction
|
||||
IfcStore.redo()
|
||||
purge_module_data()
|
||||
IfcStore.update_undo_redo_stack_objects()
|
||||
IfcStore.reload_linked_elements(objects=[bpy.data.objects.get(o) for o in IfcStore.undo_redo_stack_objects])
|
||||
IfcStore.track_undo_redo_stack_selected_objects()
|
||||
IfcStore.reload_undo_redo_stack_objects()
|
||||
|
||||
|
||||
@persistent
|
||||
|
||||
@@ -43,6 +43,7 @@ class IfcStore:
|
||||
library_file = None
|
||||
element_listeners = set()
|
||||
undo_redo_stack_objects = set()
|
||||
undo_redo_stack_object_names = {}
|
||||
current_transaction = ""
|
||||
last_transaction = ""
|
||||
history = []
|
||||
@@ -147,14 +148,32 @@ class IfcStore:
|
||||
IfcStore.element_listeners.add(callback)
|
||||
|
||||
@staticmethod
|
||||
def update_undo_redo_stack_objects():
|
||||
def track_undo_redo_stack_object_map():
|
||||
"""Keeps track of currently mapped object names, typically during undo and redo
|
||||
|
||||
When any Blender object is stored outside a Blender PointerProperty, such as
|
||||
in a regular Python list, there is the likely probability that the object
|
||||
will be invalidated when undo or redo occurs. Object invalidation seems to
|
||||
occur whenever an object is affected during an operation.
|
||||
|
||||
For example, if an operator deletes a modifier on o1, then o1 will be invalidated.
|
||||
"""
|
||||
for key, value in IfcStore.id_map.items():
|
||||
try:
|
||||
IfcStore.undo_redo_stack_object_names[key] = value.name
|
||||
except:
|
||||
continue
|
||||
|
||||
@staticmethod
|
||||
def track_undo_redo_stack_selected_objects():
|
||||
"""Keeps track of selected object names, typically during undo and redo
|
||||
|
||||
When any Blender object is stored outside a Blender PointerProperty, such as
|
||||
in a regular Python list, there is the likely probability that the object
|
||||
will be invalidated when undo or redo occurs. Object invalidation seems to
|
||||
only occur for selected objects either pre/post undo/redo event, including
|
||||
selected objects for consecutive undo/redos, and all children.
|
||||
occur for selected objects either pre/post undo/redo event, including
|
||||
selected objects for consecutive undo/redos, and all children. This is
|
||||
important because selected objects are often deleted from the scene.
|
||||
|
||||
So if I first select o1, then o2, then o3, then press undo, o3 will be
|
||||
invalidated. If instead I press undo twice, o3 and o2 will be invalidated.
|
||||
@@ -169,14 +188,23 @@ class IfcStore:
|
||||
IfcStore.undo_redo_stack_objects |= objects
|
||||
|
||||
@staticmethod
|
||||
def reload_linked_elements(objects=None):
|
||||
def reload_undo_redo_stack_objects():
|
||||
"""Reloads any invalidated objects after undo or redo
|
||||
|
||||
After an undo or redo operation, objects may have been invalidated in
|
||||
our id_map and guid_map. Invalidated objects are typically those that
|
||||
have been manipulated or deleted. This checks the cache of mapped and
|
||||
selected objects prior to the operation and ensures that if the object
|
||||
is invalidated, they are reloaded based on the object name that was
|
||||
tracked prior to the undo / redo.
|
||||
"""
|
||||
file = IfcStore.get_file()
|
||||
if not file:
|
||||
return
|
||||
if objects is None:
|
||||
objects = bpy.data.objects
|
||||
|
||||
for obj in objects:
|
||||
# First, reload objects that were selected or active
|
||||
for name in IfcStore.undo_redo_stack_objects:
|
||||
obj = bpy.data.objects.get(name)
|
||||
if not obj:
|
||||
continue
|
||||
if not obj.BIMObjectProperties.ifc_definition_id:
|
||||
@@ -187,6 +215,38 @@ class IfcStore:
|
||||
data["guid"] = element.GlobalId
|
||||
IfcStore.commit_link_element(data)
|
||||
|
||||
# Scan for any straggling invalidated objects which were indirectly affected and reload them too.
|
||||
for key, value in IfcStore.id_map.items():
|
||||
try:
|
||||
value.name
|
||||
except:
|
||||
obj = bpy.data.objects.get(IfcStore.undo_redo_stack_object_names[key])
|
||||
if not obj or not obj.BIMObjectProperties.ifc_definition_id:
|
||||
continue
|
||||
element = file.by_id(obj.BIMObjectProperties.ifc_definition_id)
|
||||
data = {"id": element.id(), "obj": obj.name}
|
||||
if hasattr(element, "GlobalId"):
|
||||
data["guid"] = element.GlobalId
|
||||
IfcStore.commit_link_element(data)
|
||||
|
||||
|
||||
@staticmethod
|
||||
def relink_all_objects():
|
||||
file = IfcStore.get_file()
|
||||
if not file:
|
||||
return
|
||||
for obj in bpy.data.objects:
|
||||
if not obj:
|
||||
continue
|
||||
if not obj.BIMObjectProperties.ifc_definition_id:
|
||||
continue
|
||||
element = file.by_id(obj.BIMObjectProperties.ifc_definition_id)
|
||||
data = {"id": element.id(), "obj": obj.name}
|
||||
if hasattr(element, "GlobalId"):
|
||||
data["guid"] = element.GlobalId
|
||||
IfcStore.commit_link_element(data)
|
||||
|
||||
|
||||
@staticmethod
|
||||
def delete_element(element):
|
||||
IfcStore.deleted_ids.add(element.id())
|
||||
@@ -312,6 +372,7 @@ class IfcStore:
|
||||
@staticmethod
|
||||
def begin_transaction(operator):
|
||||
IfcStore.undo_redo_stack_objects = set()
|
||||
IfcStore.undo_redo_stack_object_names = {}
|
||||
IfcStore.current_transaction = str(uuid.uuid4())
|
||||
operator.transaction_key = IfcStore.current_transaction
|
||||
|
||||
|
||||
@@ -1552,6 +1552,10 @@ class IfcImporter:
|
||||
"STAIR_ARROW",
|
||||
"TEXT",
|
||||
"TEXT_LEADER",
|
||||
"LEADER",
|
||||
"HATCH",
|
||||
"LINEWORK",
|
||||
"AREA",
|
||||
]
|
||||
|
||||
def get_element_matrix(self, element):
|
||||
@@ -1678,6 +1682,12 @@ class IfcImporter:
|
||||
else:
|
||||
camera.BIMCameraProperties.diagram_scale = "CUSTOM"
|
||||
camera.BIMCameraProperties.custom_diagram_scale = pset["Scale"]
|
||||
if "HasUnderlay" in pset:
|
||||
camera.BIMCameraProperties.has_underlay = pset["HasUnderlay"]
|
||||
if "HasLinework" in pset:
|
||||
camera.BIMCameraProperties.has_linework = pset["HasLinework"]
|
||||
if "HasAnnotation" in pset:
|
||||
camera.BIMCameraProperties.has_annotation = pset["HasAnnotation"]
|
||||
return camera
|
||||
|
||||
def create_curve(self, element, shape):
|
||||
|
||||
@@ -119,9 +119,10 @@ class CreateDrawing(bpy.types.Operator):
|
||||
start = time.time()
|
||||
self.profile_code("Start drawing generation process")
|
||||
self.props = context.scene.DocProperties
|
||||
self.cprops = self.camera.data.BIMCameraProperties
|
||||
self.drawing_name = self.file.by_id(self.camera.BIMObjectProperties.ifc_definition_id).Name
|
||||
self.get_scale()
|
||||
if self.camera.data.BIMCameraProperties.update_representation(self.camera):
|
||||
if self.cprops.update_representation(self.camera):
|
||||
bpy.ops.bim.update_representation(obj=self.camera.name, ifc_representation_class="")
|
||||
underlay_svg = self.generate_underlay(context)
|
||||
self.profile_code("Generate underlay")
|
||||
@@ -186,12 +187,12 @@ class CreateDrawing(bpy.types.Operator):
|
||||
return svg_path
|
||||
|
||||
def generate_underlay(self, context):
|
||||
if not self.props.has_underlay:
|
||||
if not self.cprops.has_underlay:
|
||||
return
|
||||
svg_path = os.path.join(context.scene.BIMProperties.data_dir, "cache", self.drawing_name + "-underlay.svg")
|
||||
context.scene.render.filepath = svg_path[0:-4] + ".png"
|
||||
drawing_style = context.scene.DocProperties.drawing_styles[
|
||||
self.camera.data.BIMCameraProperties.active_drawing_style_index
|
||||
self.cprops.active_drawing_style_index
|
||||
]
|
||||
|
||||
if drawing_style.render_type == "DEFAULT":
|
||||
@@ -246,7 +247,7 @@ class CreateDrawing(bpy.types.Operator):
|
||||
return svg_path
|
||||
|
||||
def generate_linework(self, context):
|
||||
if not self.props.has_linework:
|
||||
if not self.cprops.has_linework:
|
||||
return
|
||||
svg_path = os.path.join(context.scene.BIMProperties.data_dir, "cache", self.drawing_name + "-linework.svg")
|
||||
if os.path.isfile(svg_path) and self.props.should_use_linework_cache:
|
||||
@@ -410,7 +411,7 @@ class CreateDrawing(bpy.types.Operator):
|
||||
return re.sub("[^0-9a-zA-Z]+", "", name)
|
||||
|
||||
def generate_annotation(self, context):
|
||||
if not self.props.has_annotation:
|
||||
if not self.cprops.has_annotation:
|
||||
return
|
||||
svg_path = os.path.join(context.scene.BIMProperties.data_dir, "cache", self.drawing_name + "-annotation.svg")
|
||||
if os.path.isfile(svg_path) and self.props.should_use_annotation_cache:
|
||||
@@ -420,7 +421,7 @@ class CreateDrawing(bpy.types.Operator):
|
||||
svg_writer = svgwriter.SvgWriter()
|
||||
|
||||
drawing_style = context.scene.DocProperties.drawing_styles[
|
||||
camera.data.BIMCameraProperties.active_drawing_style_index
|
||||
self.cprops.active_drawing_style_index
|
||||
]
|
||||
|
||||
render = context.scene.render
|
||||
@@ -473,7 +474,7 @@ class CreateDrawing(bpy.types.Operator):
|
||||
svg_writer.annotations["section_level_obj"] = obj
|
||||
elif element.ObjectType == "TEXT":
|
||||
svg_writer.annotations.setdefault("text_objs", []).append(obj)
|
||||
elif element.ObjectType == "MISC":
|
||||
else:
|
||||
svg_writer.annotations.setdefault("misc_objs", []).append(obj)
|
||||
|
||||
svg_writer.annotations["attributes"] = [a.name for a in drawing_style.attributes]
|
||||
@@ -635,14 +636,8 @@ class RemoveDrawingFromSheet(bpy.types.Operator):
|
||||
|
||||
def execute(self, context):
|
||||
reference = tool.Ifc.get().by_id(self.reference)
|
||||
if tool.Ifc.get_schema() == "IFC2X3":
|
||||
sheet = reference.ReferenceToDocument[0]
|
||||
drawing = [r for r in tool.Ifc.by_type("IfcRelAssociatesDocument") if r.RelatingDocument == reference][
|
||||
0
|
||||
].RelatedObjects[0]
|
||||
else:
|
||||
sheet = reference.ReferencedDocument
|
||||
drawing = reference.DocumentRefForObjects[0].RelatedObjects[0]
|
||||
sheet = tool.Drawing.get_reference_sheet(reference)
|
||||
drawing = tool.Drawing.get_reference_element(reference)
|
||||
|
||||
tool.Ifc.run("document.unassign_document", product=drawing, document=reference)
|
||||
tool.Ifc.run("document.remove_reference", reference=reference)
|
||||
@@ -667,10 +662,11 @@ class CreateSheets(bpy.types.Operator):
|
||||
scene = context.scene
|
||||
props = scene.DocProperties
|
||||
active_sheet = props.sheets[props.active_sheet_index]
|
||||
name = active_sheet.name
|
||||
sheet = tool.Ifc.get().by_id(active_sheet.ifc_definition_id)
|
||||
name = tool.Drawing.get_sheet_filename(sheet)
|
||||
sheet_builder = sheeter.SheetBuilder()
|
||||
sheet_builder.data_dir = scene.BIMProperties.data_dir
|
||||
sheet_builder.build(name)
|
||||
sheet_builder.build(sheet)
|
||||
|
||||
svg2pdf_command = context.preferences.addons["blenderbim"].preferences.svg2pdf_command
|
||||
svg2dxf_command = context.preferences.addons["blenderbim"].preferences.svg2dxf_command
|
||||
|
||||
@@ -72,8 +72,10 @@ def update_diagram_scale(self, context):
|
||||
scale = self.diagram_scale
|
||||
if scale == "CUSTOM":
|
||||
scale = self.custom_diagram_scale
|
||||
scale = scale.split("|")[1]
|
||||
element = tool.Ifc.get_entity(context.scene.camera)
|
||||
if "|" not in scale:
|
||||
return
|
||||
human_scale, scale = scale.split("|")
|
||||
element = tool.Ifc.get_entity(context.active_object)
|
||||
if not element:
|
||||
return
|
||||
pset = ifcopenshell.util.element.get_psets(element).get("EPset_Drawing")
|
||||
@@ -81,7 +83,9 @@ 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})
|
||||
ifcopenshell.api.run(
|
||||
"pset.edit_pset", tool.Ifc.get(), pset=pset, properties={"Scale": scale, "HumanScale": human_scale}
|
||||
)
|
||||
|
||||
|
||||
def get_diagram_scales(self, context):
|
||||
@@ -149,6 +153,30 @@ def update_drawing_name(self, context):
|
||||
core.update_drawing_name(tool.Ifc, tool.Drawing, drawing=drawing, name=self.name)
|
||||
|
||||
|
||||
def update_has_underlay(self, context):
|
||||
update_layer(self, context, "HasUnderlay", self.has_underlay)
|
||||
|
||||
|
||||
def update_has_linework(self, context):
|
||||
update_layer(self, context, "HasLinework", self.has_linework)
|
||||
|
||||
|
||||
def update_has_annotation(self, context):
|
||||
update_layer(self, context, "HasAnnotation", self.has_annotation)
|
||||
|
||||
|
||||
def update_layer(self, context, name, value):
|
||||
element = tool.Ifc.get_entity(context.active_object)
|
||||
if not element:
|
||||
return
|
||||
pset = ifcopenshell.util.element.get_psets(element).get("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={name: value})
|
||||
|
||||
|
||||
def getTitleblocks(self, context):
|
||||
global titleblocks_enum
|
||||
if len(titleblocks_enum) < 1:
|
||||
@@ -272,9 +300,6 @@ class RasterStyleProperty(enum.Enum):
|
||||
|
||||
|
||||
class DocProperties(PropertyGroup):
|
||||
has_underlay: BoolProperty(name="Underlay", default=False)
|
||||
has_linework: BoolProperty(name="Linework", default=True)
|
||||
has_annotation: BoolProperty(name="Annotation", default=True)
|
||||
should_use_underlay_cache: BoolProperty(name="Use Underlay Cache", default=False)
|
||||
should_use_linework_cache: BoolProperty(name="Use Linework Cache", default=False)
|
||||
should_use_annotation_cache: BoolProperty(name="Use Annotation Cache", default=False)
|
||||
@@ -316,6 +341,9 @@ class DocProperties(PropertyGroup):
|
||||
|
||||
|
||||
class BIMCameraProperties(PropertyGroup):
|
||||
has_underlay: BoolProperty(name="Underlay", default=False, update=update_has_underlay)
|
||||
has_linework: BoolProperty(name="Linework", default=True, update=update_has_linework)
|
||||
has_annotation: BoolProperty(name="Annotation", default=True, update=update_has_annotation)
|
||||
representation: StringProperty(name="Representation")
|
||||
view_name: StringProperty(name="View Name")
|
||||
diagram_scale: EnumProperty(items=get_diagram_scales, name="Drawing Scale", update=update_diagram_scale)
|
||||
|
||||
@@ -67,7 +67,7 @@ class SheetBuilder:
|
||||
drawing_dir = os.path.join(self.data_dir, "diagrams")
|
||||
sheet_path = os.path.join(sheet_dir, sheet_name + ".svg")
|
||||
drawing_path = os.path.join(drawing_dir, filename + ".svg")
|
||||
underlay_path = os.path.join(drawing_dir, filename + ".png")
|
||||
underlay_path = os.path.join(drawing_dir, filename + "-underlay.png")
|
||||
|
||||
if not os.path.isfile(sheet_path):
|
||||
raise FileNotFoundError
|
||||
@@ -92,6 +92,7 @@ class SheetBuilder:
|
||||
|
||||
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, sheet_dir)
|
||||
background.attrib["x"] = "30"
|
||||
background.attrib["y"] = "30"
|
||||
@@ -100,6 +101,7 @@ class SheetBuilder:
|
||||
|
||||
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, sheet_dir)
|
||||
foreground.attrib["x"] = "30"
|
||||
foreground.attrib["y"] = "30"
|
||||
@@ -119,9 +121,7 @@ class SheetBuilder:
|
||||
sheet_tree = ET.parse(sheet_path)
|
||||
sheet_root = sheet_tree.getroot()
|
||||
|
||||
print('removing drawing', sheet_root.findall("{http://www.w3.org/2000/svg}g"))
|
||||
for g in sheet_root.findall("{http://www.w3.org/2000/svg}g"):
|
||||
print('checking g', g)
|
||||
if g.attrib["data-type"] == "drawing" and g.attrib["data-guid"] == drawing.GlobalId:
|
||||
sheet_root.remove(g)
|
||||
break
|
||||
@@ -160,13 +160,15 @@ class SheetBuilder:
|
||||
title_tree = ET.parse(os.path.join(self.data_dir, "templates", "view-title.svg"))
|
||||
title_root = title_tree.getroot()
|
||||
title = ET.SubElement(parent, "image")
|
||||
title.attrib["data-type"] = "view-title"
|
||||
title.attrib["xlink:href"] = "../templates/view-title.svg"
|
||||
title.attrib["x"] = str(x)
|
||||
title.attrib["y"] = str(y)
|
||||
title.attrib["width"] = str(self.convert_to_mm(title_root.attrib.get("width")))
|
||||
title.attrib["height"] = str(self.convert_to_mm(title_root.attrib.get("height")))
|
||||
|
||||
def build(self, sheet_name):
|
||||
def build(self, sheet):
|
||||
sheet_name = tool.Drawing.get_sheet_filename(sheet)
|
||||
os.makedirs(os.path.join(self.data_dir, "build", sheet_name), exist_ok=True)
|
||||
|
||||
sheet_path = os.path.join(self.data_dir, "sheets", f"{sheet_name}.svg")
|
||||
@@ -177,48 +179,60 @@ class SheetBuilder:
|
||||
tree = ET.parse(sheet_path)
|
||||
root = tree.getroot()
|
||||
|
||||
titleblock = root.findall('{http://www.w3.org/2000/svg}g[@data-type="titleblock"]')[0]
|
||||
image = titleblock.findall("{http://www.w3.org/2000/svg}image")[0]
|
||||
titleblock.append(self.parse_embedded_svg(image, {"number": sheet_name, "revision": "A"}))
|
||||
titleblock.remove(image)
|
||||
|
||||
self.group_number = 1
|
||||
self.build_drawings(root.findall('{http://www.w3.org/2000/svg}g[@data-type="drawing"]'), sheet_name)
|
||||
self.build_titleblock(root, sheet)
|
||||
self.build_drawings(root, sheet)
|
||||
self.build_schedules(root.findall('{http://www.w3.org/2000/svg}g[@data-type="schedule"]'))
|
||||
|
||||
with open(os.path.join(self.data_dir, "build", sheet_name, f"{sheet_name}.svg"), "wb") as output:
|
||||
tree.write(output)
|
||||
|
||||
def build_drawings(self, drawings, sheet_name):
|
||||
for view in drawings:
|
||||
def build_titleblock(self, root, sheet):
|
||||
titleblock = root.findall('{http://www.w3.org/2000/svg}g[@data-type="titleblock"]')[0]
|
||||
image = titleblock.findall("{http://www.w3.org/2000/svg}image")[0]
|
||||
titleblock.append(self.parse_embedded_svg(image, sheet.get_info()))
|
||||
titleblock.remove(image)
|
||||
|
||||
def build_drawings(self, root, sheet):
|
||||
sheet_name = tool.Drawing.get_sheet_filename(sheet)
|
||||
references = tool.Drawing.get_document_references(sheet)
|
||||
drawing_references = {tool.Drawing.get_reference_element(r): r for r in references}
|
||||
|
||||
for view in root.findall('{http://www.w3.org/2000/svg}g[@data-type="drawing"]'):
|
||||
drawing = [d for d in drawing_references.keys() if d and d.GlobalId == view.attrib["data-guid"]][0]
|
||||
reference = drawing_references[drawing]
|
||||
|
||||
images = view.findall("{http://www.w3.org/2000/svg}image")
|
||||
background = images[0]
|
||||
foreground = images[1]
|
||||
view_title = images[2]
|
||||
self.scale = "NTS"
|
||||
|
||||
# Add foreground
|
||||
view.append(self.parse_embedded_svg(foreground, {}))
|
||||
background = None
|
||||
foreground = None
|
||||
view_title = None
|
||||
|
||||
# Add background
|
||||
background_path = os.path.join(self.data_dir, "sheets", self.get_href(background))
|
||||
for image in images:
|
||||
if image.attrib["data-type"] == "background":
|
||||
background = image
|
||||
elif image.attrib["data-type"] == "foreground":
|
||||
foreground = image
|
||||
elif image.attrib["data-type"] == "view-title":
|
||||
view_title = image
|
||||
|
||||
copy(background_path, os.path.join(self.data_dir, "build", sheet_name))
|
||||
if foreground is not None:
|
||||
view.append(self.parse_embedded_svg(foreground, {}))
|
||||
|
||||
# Add view title
|
||||
foreground_path = self.get_href(foreground)
|
||||
view.append(
|
||||
self.parse_embedded_svg(
|
||||
view_title,
|
||||
{"no": self.group_number, "name": ntpath.basename(foreground_path)[0:-4], "scale": self.scale},
|
||||
)
|
||||
)
|
||||
if background is not None:
|
||||
background_path = os.path.join(self.data_dir, "sheets", self.get_href(background))
|
||||
copy(background_path, os.path.join(self.data_dir, "build", sheet_name))
|
||||
|
||||
if view_title is not None:
|
||||
foreground_path = self.get_href(foreground)
|
||||
data = reference.get_info()
|
||||
if not data["Name"]:
|
||||
data["Name"] = ntpath.basename(foreground_path)[0:-4]
|
||||
data["Scale"] = tool.Drawing.get_drawing_human_scale(drawing)
|
||||
view.append(self.parse_embedded_svg(view_title, data))
|
||||
|
||||
for image in images:
|
||||
view.remove(image)
|
||||
|
||||
self.group_number += 1
|
||||
|
||||
def build_schedules(self, schedules):
|
||||
for group in schedules:
|
||||
images = group.findall("{http://www.w3.org/2000/svg}image")
|
||||
@@ -231,15 +245,13 @@ class SheetBuilder:
|
||||
path = self.get_href(schedule)
|
||||
group.append(
|
||||
self.parse_embedded_svg(
|
||||
group_title, {"no": self.group_number, "name": ntpath.basename(path)[0:-4], "scale": self.scale}
|
||||
group_title, {"no": 1, "name": ntpath.basename(path)[0:-4], "scale": self.scale}
|
||||
)
|
||||
)
|
||||
|
||||
for image in images:
|
||||
group.remove(image)
|
||||
|
||||
self.group_number += 1
|
||||
|
||||
def get_href(self, element):
|
||||
return urllib.parse.unquote(element.attrib.get("{http://www.w3.org/1999/xlink}href"))
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ import pystache
|
||||
import xml.etree.ElementTree as ET
|
||||
import svgwrite
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.element
|
||||
import ifcopenshell.util.representation
|
||||
import blenderbim.tool as tool
|
||||
import blenderbim.bim.module.drawing.helper as helper
|
||||
@@ -148,13 +149,10 @@ class SvgWriter:
|
||||
self.draw_dimension_annotations(obj)
|
||||
self.draw_measureit_arch_dimension_annotations()
|
||||
|
||||
if self.annotations.get("break_obj"):
|
||||
self.draw_break_annotations(self.annotations["break_obj"])
|
||||
|
||||
for grid_obj in self.annotations.get("grid_objs", []):
|
||||
matrix_world = grid_obj.matrix_world
|
||||
classes = self.get_attribute_classes(grid_obj)
|
||||
for edge in grid_obj.data.edges:
|
||||
classes = ["annotation", "grid"]
|
||||
v0_global = matrix_world @ grid_obj.data.vertices[edge.vertices[0]].co.xyz
|
||||
v1_global = matrix_world @ grid_obj.data.vertices[edge.vertices[1]].co.xyz
|
||||
v0 = self.project_point_onto_camera(v0_global)
|
||||
@@ -167,8 +165,6 @@ class SvgWriter:
|
||||
start=tuple(start * self.scale), end=tuple(end * self.scale), class_=" ".join(classes)
|
||||
)
|
||||
)
|
||||
line["marker-start"] = "url(#grid-marker)"
|
||||
line["marker-end"] = "url(#grid-marker)"
|
||||
line["stroke-dasharray"] = "12.5, 3, 3, 3"
|
||||
axis_tag = tool.Ifc.get_entity(grid_obj).Name
|
||||
self.svg.add(
|
||||
@@ -201,13 +197,13 @@ class SvgWriter:
|
||||
self.draw_ifc_annotation()
|
||||
|
||||
for obj in self.annotations.get("misc_objs", []):
|
||||
self.draw_misc_annotation(obj, ["IfcAnnotation"])
|
||||
self.draw_misc_annotation(obj)
|
||||
|
||||
for obj_data in self.annotations.get("hidden_objs", []):
|
||||
self.draw_line_annotation(obj_data, ["hidden"])
|
||||
self.draw_line_annotation(obj_data)
|
||||
|
||||
for obj_data in self.annotations.get("solid_objs", []):
|
||||
self.draw_line_annotation(obj_data, ["solid"])
|
||||
self.draw_line_annotation(obj_data)
|
||||
|
||||
self.draw_leader_annotations()
|
||||
|
||||
@@ -216,8 +212,8 @@ class SvgWriter:
|
||||
|
||||
if self.annotations.get("section_level_obj"):
|
||||
matrix_world = self.annotations["section_level_obj"].matrix_world
|
||||
classes = self.get_attribute_classes(self.annotations["section_level_obj"])
|
||||
for spline in self.annotations["section_level_obj"].data.splines:
|
||||
classes = ["annotation", "section-level"]
|
||||
points = self.get_spline_points(spline)
|
||||
projected_points = [self.project_point_onto_camera(matrix_world @ p.co.xyz) for p in points]
|
||||
d = " ".join(
|
||||
@@ -228,8 +224,6 @@ class SvgWriter:
|
||||
)
|
||||
d = "M{}".format(d[1:])
|
||||
path = self.svg.add(self.svg.path(d=d, class_=" ".join(classes)))
|
||||
path["marker-start"] = "url(#section-level-marker)"
|
||||
path["stroke-dasharray"] = "12.5, 3, 3, 3"
|
||||
text_position = Vector(
|
||||
(
|
||||
(x_offset + projected_points[0].x) * self.scale,
|
||||
@@ -258,8 +252,8 @@ class SvgWriter:
|
||||
|
||||
if self.annotations.get("stair_obj"):
|
||||
matrix_world = self.annotations["stair_obj"].matrix_world
|
||||
classes = self.get_attribute_classes(self.annotations["stair_obj"])
|
||||
for spline in self.annotations["stair_obj"].data.splines:
|
||||
classes = ["annotation", "stair"]
|
||||
points = self.get_spline_points(spline)
|
||||
projected_points = [self.project_point_onto_camera(matrix_world @ p.co.xyz) for p in points]
|
||||
d = " ".join(
|
||||
@@ -296,6 +290,9 @@ class SvgWriter:
|
||||
for text_obj in self.annotations.get("text_objs", []):
|
||||
self.draw_text_annotation(text_obj, text_obj.location)
|
||||
|
||||
if self.annotations.get("break_obj"):
|
||||
self.draw_break_annotations(self.annotations["break_obj"])
|
||||
|
||||
def draw_ifc_annotation(self):
|
||||
x_offset = self.raw_width / 2
|
||||
y_offset = self.raw_height / 2
|
||||
@@ -316,13 +313,13 @@ class SvgWriter:
|
||||
)
|
||||
)
|
||||
|
||||
def draw_misc_annotation(self, obj, classes):
|
||||
def draw_misc_annotation(self, obj):
|
||||
# We have to decide whether this should come from Blender or from IFC.
|
||||
# For the moment, for convenience of experimenting with ideas, it comes
|
||||
# from Blender. In the future, it should probably come from IFC.
|
||||
if not isinstance(obj.data, bpy.types.Mesh):
|
||||
return
|
||||
classes.extend(self.get_attribute_classes(obj))
|
||||
classes = self.get_attribute_classes(obj)
|
||||
if len(obj.data.polygons) == 0:
|
||||
self.draw_edge_annotation(obj, classes)
|
||||
return
|
||||
@@ -332,6 +329,7 @@ class SvgWriter:
|
||||
for polygon in obj.data.polygons:
|
||||
points = [obj.data.vertices[v] for v in polygon.vertices]
|
||||
projected_points = [self.project_point_onto_camera(matrix_world @ p.co.xyz) for p in points]
|
||||
projected_points.append(projected_points[0])
|
||||
d = " ".join(
|
||||
[
|
||||
"L {} {}".format((x_offset + p.x) * self.scale, (y_offset - p.y) * self.scale)
|
||||
@@ -342,12 +340,12 @@ class SvgWriter:
|
||||
path = self.svg.add(self.svg.path(d=d, class_=" ".join(classes)))
|
||||
|
||||
def get_attribute_classes(self, obj):
|
||||
classes = [obj.name.split("/")[0]]
|
||||
for slot in obj.material_slots:
|
||||
if slot.material:
|
||||
classes.append("material-{}".format(re.sub("[^0-9a-zA-Z]+", "", slot.material.name)))
|
||||
global_id = IfcStore.get_file().by_id(obj.BIMObjectProperties.ifc_definition_id).GlobalId
|
||||
classes.append("globalid-{}".format(global_id))
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
global_id = "GlobalId-{}".format(element.GlobalId)
|
||||
predefined_type = "PredefinedType-" + self.canonicalise_class_name(
|
||||
str(ifcopenshell.util.element.get_predefined_type(element))
|
||||
)
|
||||
classes = [global_id, element.is_a(), predefined_type]
|
||||
for attribute in self.annotations.get("attributes", []):
|
||||
result = self.get_obj_value(obj, attribute)
|
||||
if result:
|
||||
@@ -356,6 +354,9 @@ class SvgWriter:
|
||||
)
|
||||
return classes
|
||||
|
||||
def canonicalise_class_name(self, name):
|
||||
return re.sub("[^0-9a-zA-Z]+", "", name)
|
||||
|
||||
def get_obj_value(self, obj, key):
|
||||
# This is a duplicate implementation of the IFC selector key in Blender
|
||||
# In the future if all this becomes purely IFC based this can be deleted
|
||||
@@ -381,14 +382,14 @@ class SvgWriter:
|
||||
if result:
|
||||
return result.string_value
|
||||
|
||||
def draw_line_annotation(self, obj_data, classes):
|
||||
def draw_line_annotation(self, obj_data):
|
||||
# TODO: properly scope these offsets
|
||||
x_offset = self.raw_width / 2
|
||||
y_offset = self.raw_height / 2
|
||||
|
||||
obj, data = obj_data
|
||||
|
||||
classes.extend(["annotation"])
|
||||
classes = self.get_attribute_classes(obj)
|
||||
matrix_world = obj.matrix_world
|
||||
|
||||
if isinstance(data, bpy.types.Curve):
|
||||
@@ -424,7 +425,7 @@ class SvgWriter:
|
||||
|
||||
def draw_leader_annotations(self):
|
||||
for obj in self.annotations.get("leader_objs", []):
|
||||
self.draw_line_annotation((obj, obj.data), ["leader"])
|
||||
self.draw_line_annotation((obj, obj.data))
|
||||
spline = obj.data.splines[0]
|
||||
spline_points = spline.bezier_points if spline.bezier_points else spline.points
|
||||
if spline_points:
|
||||
@@ -437,7 +438,7 @@ class SvgWriter:
|
||||
x_offset = self.raw_width / 2
|
||||
y_offset = self.raw_height / 2
|
||||
|
||||
self.draw_line_annotation((obj, obj.data), ["solid"])
|
||||
self.draw_line_annotation((obj, obj.data))
|
||||
|
||||
v1 = self.project_point_onto_camera(obj.matrix_world @ Vector((0, 0, 0)))
|
||||
v2 = self.project_point_onto_camera(obj.matrix_world @ Vector((0, 0, -1)))
|
||||
@@ -453,9 +454,23 @@ class SvgWriter:
|
||||
(symbol_position * self.scale)[1],
|
||||
)
|
||||
|
||||
self.svg.add(self.svg.use("#section-arrow", insert=tuple(symbol_position * self.scale), transform=transform))
|
||||
self.svg.add(
|
||||
self.svg.use("#section-arrow", insert=tuple(symbol_position * self.scale), transform=transform)
|
||||
)
|
||||
self.svg.add(self.svg.use("#section-tag", insert=tuple(symbol_position * self.scale)))
|
||||
|
||||
reference_id, sheet_id = self.get_reference_and_sheet_id_from_annotation(tool.Ifc.get_entity(obj))
|
||||
text_position = list(symbol_position * self.scale)
|
||||
text_style = {
|
||||
"font-size": annotation.Annotator.get_svg_text_size(2.5),
|
||||
"font-family": "OpenGost Type B TT",
|
||||
"text-anchor": "middle",
|
||||
"alignment-baseline": "middle",
|
||||
"dominant-baseline": "middle",
|
||||
}
|
||||
self.svg.add(self.svg.text(reference_id, insert=(text_position[0], text_position[1] - 2.5), **text_style))
|
||||
self.svg.add(self.svg.text(sheet_id, insert=(text_position[0], text_position[1] + 2.5), **text_style))
|
||||
|
||||
def draw_elevation_annotation(self, obj):
|
||||
x_offset = self.raw_width / 2
|
||||
y_offset = self.raw_height / 2
|
||||
@@ -475,6 +490,34 @@ class SvgWriter:
|
||||
self.svg.add(self.svg.use("#elevation-arrow", insert=tuple(symbol_position * self.scale), transform=transform))
|
||||
self.svg.add(self.svg.use("#elevation-tag", insert=tuple(symbol_position * self.scale)))
|
||||
|
||||
reference_id, sheet_id = self.get_reference_and_sheet_id_from_annotation(tool.Ifc.get_entity(obj))
|
||||
text_position = list(symbol_position * self.scale)
|
||||
text_style = {
|
||||
"font-size": annotation.Annotator.get_svg_text_size(2.5),
|
||||
"font-family": "OpenGost Type B TT",
|
||||
"text-anchor": "middle",
|
||||
"alignment-baseline": "middle",
|
||||
"dominant-baseline": "middle",
|
||||
}
|
||||
self.svg.add(self.svg.text(reference_id, insert=(text_position[0], text_position[1] - 2.5), **text_style))
|
||||
self.svg.add(self.svg.text(sheet_id, insert=(text_position[0], text_position[1] + 2.5), **text_style))
|
||||
|
||||
def get_reference_and_sheet_id_from_annotation(self, element):
|
||||
reference_id = "-"
|
||||
sheet_id = "-"
|
||||
drawing = tool.Drawing.get_annotation_element(element)
|
||||
reference = tool.Drawing.get_drawing_reference(drawing)
|
||||
if reference:
|
||||
sheet = tool.Drawing.get_reference_sheet(reference)
|
||||
if sheet:
|
||||
if tool.Ifc.get_schema() == "IFC2X3":
|
||||
reference_id = reference.ItemReference or "-"
|
||||
sheet_id = sheet.DocumentId or "-"
|
||||
else:
|
||||
reference_id = reference.Identification or "-"
|
||||
sheet_id = sheet.Identification or "-"
|
||||
return (reference_id, sheet_id)
|
||||
return ("-", "-")
|
||||
|
||||
def draw_text_annotation(self, text_obj, position):
|
||||
x_offset = self.raw_width / 2
|
||||
@@ -502,7 +545,7 @@ class SvgWriter:
|
||||
|
||||
if text_obj.BIMTextProperties.symbol != "None":
|
||||
self.svg.add(
|
||||
self.svg.use("#{}".format(text_obj.BIMTextProperties.symbol), insert=tuple(text_position * self.scale))
|
||||
self.svg.use(f"#{text_obj.BIMTextProperties.symbol}", insert=tuple(text_position * self.scale))
|
||||
)
|
||||
|
||||
if text_literal.BoxAlignment == "top-left":
|
||||
@@ -533,11 +576,15 @@ class SvgWriter:
|
||||
alignment_baseline = "baseline"
|
||||
text_anchor = "end"
|
||||
|
||||
text_body = text_literal.Literal
|
||||
if text_obj.name in self.annotations.get("template_variables", {}):
|
||||
text_body = pystache.render(text_body, self.annotations["template_variables"][text_obj.name])
|
||||
literal = text_literal.Literal
|
||||
|
||||
for line_number, text_line in enumerate(text_body.split("\n")):
|
||||
product = tool.Drawing.get_text_product(element)
|
||||
selector = ifcopenshell.util.selector.Selector
|
||||
variables = {}
|
||||
for variable in re.findall("{{.*?}}", literal):
|
||||
literal = literal.replace(variable, selector.get_element_value(product, variable[2:-2]) or "")
|
||||
|
||||
for line_number, text_line in enumerate(literal.replace("\\n", "\n").split("\n")):
|
||||
self.svg.add(
|
||||
self.svg.text(
|
||||
text_line,
|
||||
@@ -586,8 +633,8 @@ class SvgWriter:
|
||||
x_offset = self.raw_width / 2
|
||||
y_offset = self.raw_height / 2
|
||||
matrix_world = obj.matrix_world
|
||||
classes = self.get_attribute_classes(obj)
|
||||
for spline in obj.data.splines:
|
||||
classes = ["annotation", "plan-level"]
|
||||
points = self.get_spline_points(spline)
|
||||
projected_points = [self.project_point_onto_camera(matrix_world @ p.co.xyz) for p in points]
|
||||
d = " ".join(
|
||||
@@ -598,7 +645,6 @@ class SvgWriter:
|
||||
)
|
||||
d = "M{}".format(d[1:])
|
||||
path = self.svg.add(self.svg.path(d=d, class_=" ".join(classes)))
|
||||
path["marker-end"] = "url(#plan-level-marker)"
|
||||
text_position = Vector(
|
||||
(
|
||||
(x_offset + projected_points[0].x) * self.scale,
|
||||
@@ -630,6 +676,7 @@ class SvgWriter:
|
||||
)
|
||||
|
||||
def draw_dimension_annotations(self, dimension_obj, text_override=None):
|
||||
classes = self.get_attribute_classes(dimension_obj)
|
||||
matrix_world = dimension_obj.matrix_world
|
||||
for spline in dimension_obj.data.splines:
|
||||
points = self.get_spline_points(spline)
|
||||
@@ -638,7 +685,7 @@ class SvgWriter:
|
||||
continue
|
||||
v0_global = matrix_world @ points[i].co.xyz
|
||||
v1_global = matrix_world @ points[i + 1].co.xyz
|
||||
self.draw_dimension_annotation(v0_global, v1_global, text_override)
|
||||
self.draw_dimension_annotation(v0_global, v1_global, classes, text_override)
|
||||
|
||||
def draw_measureit_arch_dimension_annotations(self):
|
||||
try:
|
||||
@@ -648,10 +695,11 @@ class SvgWriter:
|
||||
except:
|
||||
return
|
||||
for coord in coords:
|
||||
self.draw_dimension_annotation(Vector(coord[0]), Vector(coord[1]))
|
||||
self.draw_dimension_annotation(
|
||||
Vector(coord[0]), Vector(coord[1]), ["IfcAnnotation", "PredefinedType-DIMENSION"]
|
||||
)
|
||||
|
||||
def draw_dimension_annotation(self, v0_global, v1_global, text_override=None):
|
||||
classes = ["annotation", "dimension"]
|
||||
def draw_dimension_annotation(self, v0_global, v1_global, classes, text_override=None):
|
||||
x_offset = self.raw_width / 2
|
||||
y_offset = self.raw_height / 2
|
||||
v0 = self.project_point_onto_camera(v0_global)
|
||||
@@ -673,8 +721,6 @@ class SvgWriter:
|
||||
line = self.svg.add(
|
||||
self.svg.line(start=tuple(start * self.scale), end=tuple(end * self.scale), class_=" ".join(classes))
|
||||
)
|
||||
line["marker-start"] = "url(#dimension-marker-start)"
|
||||
line["marker-end"] = "url(#dimension-marker-end)"
|
||||
if text_override is not None:
|
||||
text = text_override
|
||||
else:
|
||||
|
||||
@@ -47,13 +47,13 @@ class BIM_PT_camera(Panel):
|
||||
|
||||
col = layout.column(align=True)
|
||||
row = col.row(align=True)
|
||||
row.prop(dprops, "has_underlay", icon="OUTLINER_OB_IMAGE")
|
||||
row.prop(props, "has_underlay", icon="OUTLINER_OB_IMAGE")
|
||||
row.prop(dprops, "should_use_underlay_cache", text="", icon="FILE_REFRESH")
|
||||
row = col.row(align=True)
|
||||
row.prop(dprops, "has_linework", icon="IMAGE_DATA")
|
||||
row.prop(props, "has_linework", icon="IMAGE_DATA")
|
||||
row.prop(dprops, "should_use_linework_cache", text="", icon="FILE_REFRESH")
|
||||
row = col.row(align=True)
|
||||
row.prop(dprops, "has_annotation", icon="MOD_EDGESPLIT")
|
||||
row.prop(props, "has_annotation", icon="MOD_EDGESPLIT")
|
||||
row.prop(dprops, "should_use_annotation_cache", text="", icon="FILE_REFRESH")
|
||||
|
||||
row = layout.row()
|
||||
|
||||
@@ -28,6 +28,7 @@ classes = (
|
||||
operator.UnlinkIfc,
|
||||
operator.UnloadLink,
|
||||
operator.LoadLink,
|
||||
operator.ToggleLinkVisibility,
|
||||
operator.SelectLibraryFile,
|
||||
operator.ChangeLibraryElement,
|
||||
operator.RefreshLibrary,
|
||||
|
||||
@@ -685,10 +685,31 @@ class LoadLink(bpy.types.Operator):
|
||||
continue
|
||||
bpy.data.scenes[0].collection.children.link(child)
|
||||
link = context.scene.BIMProjectProperties.links.get(filepath)
|
||||
link.collection_name = child.name
|
||||
link.is_loaded = True
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class ToggleLinkVisibility(bpy.types.Operator):
|
||||
bl_idname = "bim.toggle_link_visibility"
|
||||
bl_label = "Toggle Link Visibility"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Toggle visibility between SOLID and WIREFRAME"
|
||||
collection_name: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
collection_name = self.collection_name
|
||||
objs = filter(lambda obj: "IfcOpeningElement" not in obj.name, bpy.data.collections[collection_name].all_objects)
|
||||
for i,obj in enumerate(objs):
|
||||
if i == 0:
|
||||
if obj.display_type == "WIRE":
|
||||
display_type = "TEXTURED"
|
||||
else:
|
||||
display_type = "WIRE"
|
||||
obj.display_type = display_type
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class ExportIFC(bpy.types.Operator):
|
||||
bl_idname = "export_ifc.bim"
|
||||
bl_label = "Export IFC"
|
||||
|
||||
@@ -85,6 +85,7 @@ class FilterCategory(PropertyGroup):
|
||||
|
||||
class Link(PropertyGroup):
|
||||
name: StringProperty(name="Name")
|
||||
collection_name: StringProperty(name="Collection Name")
|
||||
is_loaded: BoolProperty(name="Is Loaded", default=False)
|
||||
|
||||
|
||||
|
||||
@@ -286,6 +286,8 @@ class BIM_UL_links(UIList):
|
||||
row = layout.row(align=True)
|
||||
if item.is_loaded:
|
||||
row.label(text=item.name)
|
||||
op = row.operator("bim.toggle_link_visibility", text="", icon="HIDE_OFF")
|
||||
op.collection_name = item.collection_name
|
||||
op = row.operator("bim.unload_link", text="", icon="UNLINKED")
|
||||
op.filepath = item.name
|
||||
else:
|
||||
|
||||
@@ -115,7 +115,18 @@ def add_drawing(ifc, collector, drawing, target_view=None, location_hint=None):
|
||||
ifc.run("group.assign_group", group=group, product=element)
|
||||
collector.assign(camera)
|
||||
pset = ifc.run("pset.add_pset", product=element, name="EPset_Drawing")
|
||||
ifc.run("pset.edit_pset", pset=pset, properties={"TargetView": target_view, "Scale": "1/100"})
|
||||
ifc.run(
|
||||
"pset.edit_pset",
|
||||
pset=pset,
|
||||
properties={
|
||||
"TargetView": target_view,
|
||||
"Scale": "1/100",
|
||||
"HumanScale": "1:100",
|
||||
"HasUnderlay": False,
|
||||
"HasLinework": True,
|
||||
"HasAnnotation": True,
|
||||
},
|
||||
)
|
||||
drawing.import_drawings()
|
||||
|
||||
|
||||
@@ -161,6 +172,9 @@ def add_annotation(ifc, collector, drawing_tool, drawing=None, object_type=None)
|
||||
|
||||
|
||||
def sync_references(ifc, collector, drawing_tool, drawing=None):
|
||||
if not drawing_tool.has_linework(drawing):
|
||||
return
|
||||
|
||||
context = drawing_tool.get_annotation_context(drawing_tool.get_drawing_target_view(drawing))
|
||||
if not context:
|
||||
return
|
||||
|
||||
@@ -21,6 +21,7 @@ def copy_class(ifc, collector, geometry, root, obj=None):
|
||||
element = ifc.get_entity(obj)
|
||||
if not element:
|
||||
return
|
||||
representation = root.get_object_representation(obj)
|
||||
element = ifc.run("root.copy_class", product=element)
|
||||
ifc.link(element, obj)
|
||||
relating_type = root.get_element_type(element)
|
||||
@@ -28,7 +29,6 @@ def copy_class(ifc, collector, geometry, root, obj=None):
|
||||
ifc.run("type.map_type_representations", related_object=element, relating_type=relating_type)
|
||||
root.link_object_data(ifc.get_object(relating_type), obj)
|
||||
else:
|
||||
representation = root.get_object_representation(obj)
|
||||
if representation:
|
||||
root.run_geometry_add_representation(
|
||||
obj=obj,
|
||||
|
||||
@@ -176,7 +176,7 @@ class Drawing(blenderbim.core.tool.Drawing):
|
||||
|
||||
@classmethod
|
||||
def get_drawing_target_view(cls, drawing):
|
||||
return ifcopenshell.util.element.get_psets(drawing)["EPset_Drawing"]["TargetView"]
|
||||
return ifcopenshell.util.element.get_psets(drawing)["EPset_Drawing"].get("TargetView", "MODEL_VIEW")
|
||||
|
||||
@classmethod
|
||||
def get_group_elements(cls, group):
|
||||
@@ -201,7 +201,7 @@ class Drawing(blenderbim.core.tool.Drawing):
|
||||
name = document.Identification or "X"
|
||||
else:
|
||||
name = document.DocumentId or "X"
|
||||
name += " - " + document.Name or "Unnamed"
|
||||
name += " - " + (document.Name or "Unnamed")
|
||||
return name
|
||||
|
||||
@classmethod
|
||||
@@ -291,24 +291,17 @@ class Drawing(blenderbim.core.tool.Drawing):
|
||||
if not new.is_expanded:
|
||||
continue
|
||||
|
||||
if tool.Ifc.get_schema() == "IFC2X3":
|
||||
references = sheet.DocumentReferences or []
|
||||
else:
|
||||
references = sheet.HasDocumentReferences or []
|
||||
|
||||
for reference in references:
|
||||
for reference in cls.get_document_references(sheet):
|
||||
new = props.sheets.add()
|
||||
new.ifc_definition_id = reference.id()
|
||||
new.is_sheet = False
|
||||
|
||||
if tool.Ifc.get_schema() == "IFC2X3":
|
||||
new.identification = reference.ItemReference or "X"
|
||||
element = [
|
||||
r for r in tool.Ifc.by_type("IfcRelAssociatesDocument") if r.RelatingDocument == reference
|
||||
][0].RelatedObjects[0]
|
||||
else:
|
||||
new.identification = reference.Identification or "X"
|
||||
element = reference.DocumentRefForObjects[0].RelatedObjects[0]
|
||||
|
||||
element = cls.get_reference_element(reference)
|
||||
|
||||
new.name = element.Name
|
||||
new.reference_type = "DRAWING"
|
||||
@@ -720,3 +713,43 @@ class Drawing(blenderbim.core.tool.Drawing):
|
||||
if grid_obj.matrix_world != obj.matrix_world:
|
||||
bpy.ops.bim.update_representation(obj=obj.name)
|
||||
tool.Geometry.record_object_position(obj)
|
||||
|
||||
@classmethod
|
||||
def get_document_references(cls, document):
|
||||
if tool.Ifc.get_schema() == "IFC2X3":
|
||||
return document.DocumentReferences or []
|
||||
return document.HasDocumentReferences or []
|
||||
|
||||
@classmethod
|
||||
def get_reference_element(cls, reference):
|
||||
if tool.Ifc.get_schema() == "IFC2X3":
|
||||
return [
|
||||
r for r in tool.Ifc.by_type("IfcRelAssociatesDocument") if r.RelatingDocument == reference
|
||||
][0].RelatedObjects[0]
|
||||
return reference.DocumentRefForObjects[0].RelatedObjects[0]
|
||||
|
||||
@classmethod
|
||||
def get_drawing_human_scale(cls, drawing):
|
||||
return ifcopenshell.util.element.get_psets(drawing)["EPset_Drawing"].get("HumanScale", "NTS")
|
||||
|
||||
@classmethod
|
||||
def has_linework(cls, drawing):
|
||||
return ifcopenshell.util.element.get_psets(drawing)["EPset_Drawing"].get("HasLinework", False)
|
||||
|
||||
@classmethod
|
||||
def get_annotation_element(cls, element):
|
||||
for rel in element.HasAssignments:
|
||||
if rel.is_a("IfcRelAssignsToProduct"):
|
||||
return rel.RelatingProduct
|
||||
|
||||
@classmethod
|
||||
def get_drawing_reference(cls, drawing):
|
||||
for rel in drawing.HasAssociations:
|
||||
if rel.is_a("IfcRelAssociatesDocument"):
|
||||
return rel.RelatingDocument
|
||||
|
||||
@classmethod
|
||||
def get_reference_sheet(cls, reference):
|
||||
if tool.Ifc.get_schema() == "IFC2X3":
|
||||
return reference.ReferenceToDocument[0]
|
||||
return reference.ReferencedDocument
|
||||
|
||||
@@ -58,6 +58,9 @@ class Root(blenderbim.core.tool.Root):
|
||||
def get_object_representation(cls, obj):
|
||||
if obj.data and obj.data.BIMMeshProperties.ifc_definition_id:
|
||||
return tool.Ifc.get().by_id(obj.data.BIMMeshProperties.ifc_definition_id)
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if not obj.data and getattr(element, "ObjectType", None) == "TEXT":
|
||||
return element.Representation.Representations[0]
|
||||
|
||||
@classmethod
|
||||
def get_representation_context(cls, representation):
|
||||
|
||||
@@ -63,7 +63,7 @@ architecture flow charts and diagrams. The code and its comments will guide you
|
||||
through the process.
|
||||
|
||||
Before playing with the demo module, you may want to switch to using a source
|
||||
installation. See :ref:`blenderbim/installation` for details.
|
||||
installation. See `blenderbim/installation <./installation.rst>`_ for details.
|
||||
|
||||
To see the demo module in action, you'll need to enable it. In
|
||||
``src/blenderbim/blenderbim/bim/__init__.py``, uncomment the line for the demo
|
||||
@@ -95,8 +95,8 @@ and how to test and structure it so that you can build incredibly complex
|
||||
features in a maintainable way.
|
||||
|
||||
Tests for quality checking also exist. The system is designed so that you can
|
||||
do "Test Driven Development". For reference on how to run these tests, see
|
||||
:ref:`blenderbim/running_tests` for details. You can find the tests here:
|
||||
do "Test Driven Development". For reference on how to run these tests, see `blenderbim/running_tests <./running_tests.rst>`_
|
||||
for details. You can find the tests here:
|
||||
|
||||
::
|
||||
|
||||
|
||||
@@ -5,14 +5,14 @@ Installation
|
||||
|
||||
There are different methods of installation, depending on your situation.
|
||||
|
||||
1. **Packaged installation** is recommended for regular users.
|
||||
2. **Daily build installation** is recommended for power users helping with testing.
|
||||
3. **Unpackaged installation** is recommended for package managers.
|
||||
4. **Distro installation** is recommended for those who use a Linux package manager.
|
||||
5. **Source installation** is recommended for developers.
|
||||
1. **Stable installation** is recommended for regular users.
|
||||
2. **Unstable installation** is recommended for power users helping with testing.
|
||||
3. **Building from source** is recommended for distributing a build from source.
|
||||
4. **Live development environment** is recommended for developers who are actively coding.
|
||||
5. **Distro installation** is recommended for those who use a Linux package manager.
|
||||
|
||||
Packaged installation
|
||||
---------------------
|
||||
Stable installation
|
||||
-------------------
|
||||
|
||||
The BlenderBIM Add-on is packaged like a regular Blender add-on, so installation
|
||||
is the same as any other Blender add-on. The full instructions for end-user
|
||||
@@ -24,25 +24,129 @@ Like all Blender add-ons, they can be installed using ``Edit > Preferences >
|
||||
Addons > Install > Choose Downloaded ZIP > Enable Add-on Checkbox``. You can
|
||||
enable add-ons permanently by using ``Save User Settings`` from the Addons menu.
|
||||
|
||||
Daily build installation
|
||||
------------------------
|
||||
Unstable installation
|
||||
---------------------
|
||||
|
||||
Daily builds are almost the same as **Packaged installation**, except that they
|
||||
are typically updated every day. Simply download a daily build from the `Github
|
||||
releases page <https://github.com/IfcOpenShell/IfcOpenShell/releases>`__, then
|
||||
follow the same instructions as a packaged installation.
|
||||
**Unstable installation** is almost the same as **Stable installation**, except
|
||||
that they are typically updated every day. Simply download a daily build from
|
||||
the `Github releases page
|
||||
<https://github.com/IfcOpenShell/IfcOpenShell/releases>`__, then follow the same
|
||||
instructions as a packaged installation.
|
||||
|
||||
You will need to choose which daily build to download.
|
||||
You will need to choose which build to download.
|
||||
|
||||
- If you are on Blender >=3.1, choose py310
|
||||
- If you are on Blender >=2.93 and <3.1, choose py39
|
||||
- If you are on Blender <2.93, choose py37
|
||||
- If you are on Blender >=2.93, choose py39
|
||||
- Choose linux, macos, or win depending on your operating system
|
||||
|
||||
Daily builds are not always stable. Sometimes, a build may be delayed, or
|
||||
contain broken code. We try to avoid this, but it happens.
|
||||
Sometimes, a build may be delayed, or contain broken code. We try to avoid this,
|
||||
but it happens.
|
||||
|
||||
Unpackaged installation
|
||||
-----------------------
|
||||
Building from source
|
||||
--------------------
|
||||
|
||||
It is possible to run the latest bleeding edge version of BlenderBIM without
|
||||
having to wait for an official release, since BlenderBIM is coded in Python and
|
||||
doesn't require any compilation.
|
||||
|
||||
Note that the BlenderBIM Add-on does depend on IfcOpenShell, and IfcOpenShell
|
||||
does require compilation. The following instructions will use a pre-built
|
||||
IfcOpenShell (using an IfcOpenBot build) for convenience. Instructions on how to
|
||||
compile IfcOpenShell is out of scope of this document.
|
||||
|
||||
You can create your own package by using the Makefile as shown below. You can
|
||||
choose between a ``PLATFORM`` of ``linux``, ``macos``, and ``win``. You can
|
||||
choose between a ``PYVERSION`` of ``py39``, ``py37``, or ``py310``.
|
||||
::
|
||||
|
||||
$ cd src/blenderbim
|
||||
$ make dist PLATFORM=linux PYVERSION=py310
|
||||
$ ls dist/
|
||||
|
||||
This will give you a fully packaged Blender add-on zip that you can distribute
|
||||
and install.
|
||||
|
||||
Live development environment
|
||||
----------------------------
|
||||
|
||||
One option for developers who want to actively develop from source is to follow
|
||||
the instructions from **Building from source**. However, creating a build,
|
||||
uninstalling the old add-on, and installing a new build is a slow process.
|
||||
Although it works, it is very slow, so we do not recommend it.
|
||||
|
||||
A more rapid approach is to follow the **Unstable installation** method, as this
|
||||
provides all dependencies for you out of the box. Then, we can replace certain
|
||||
Python files that tend to be updated frequently with those from the Git
|
||||
repository. We're going to use symlinks (Windows users can use ``mklink``), so
|
||||
we can code in our Git repository, and see the changes in our Blender
|
||||
installation (you will need to restart Blender to see changes).
|
||||
|
||||
In addition, we're also going to replace the Python code of the IfcOpenShell
|
||||
dependency with our Git repository, since most of the BlenderBIM Add-on
|
||||
functionality is agnostic of Blender, and is actually part of IfcOpenShell.
|
||||
Therefore, we need to keep this dependency highly updated as well.
|
||||
|
||||
The downside with this approach is that if a new dependency is added, or a
|
||||
compiled dependency version requirement has changed, or the build system
|
||||
changes, you'll need to fix your setup manually. But this is relatively rare.
|
||||
|
||||
::
|
||||
|
||||
$ git clone https://github.com/IfcOpenShell/IfcOpenShell.git
|
||||
$ cd IfcOpenShell
|
||||
|
||||
# Remove the Blender add-on Python code
|
||||
$ rm -r /path/to/blender/X.XX/scripts/addons/blenderbim/bim/
|
||||
|
||||
# Replace them with links to the Git repository
|
||||
$ ln -s src/blenderbim/blenderbim/bim /path/to/blender/X.XX/scripts/addons/blenderbim/bim
|
||||
|
||||
# Remove the IfcOpenShell dependency Python code
|
||||
$ rm -r /path/to/blender/X.XX/scripts/addons/blenderbim/libs/site/packages/ifcopenshell/api
|
||||
$ rm -r /path/to/blender/X.XX/scripts/addons/blenderbim/libs/site/packages/ifcopenshell/util
|
||||
|
||||
# Replace them with links to the Git repository
|
||||
$ ln -s src/ifcopenshell-python/ifcopenshell/api /path/to/blender/X.XX/scripts/addons/blenderbim/libs/site/packages/ifcopenshell/api
|
||||
$ ln -s src/ifcopenshell-python/ifcopenshell/util /path/to/blender/X.XX/scripts/addons/blenderbim/libs/site/packages/ifcopenshell/util
|
||||
|
||||
On Windows:
|
||||
|
||||
::
|
||||
|
||||
$ git clone https://github.com/IfcOpenShell/IfcOpenShell.git
|
||||
$ cd IfcOpenShell
|
||||
|
||||
# Remove the Blender add-on Python code
|
||||
$ rd /S /Q "\path\to\blender\X.XX\scripts\addons\blenderbim\bim\"
|
||||
|
||||
# Replace them with links to the Git repository
|
||||
$ mklink /D "\path\to\blender\X.XX\scripts\addons\blenderbim\bim" "src\blenderbim\blenderbim\bim"
|
||||
|
||||
# Remove the IfcOpenShell dependency Python code
|
||||
$ rd \S \Q "\path\to\blender\X.XX\scripts\addons\blenderbim\libs\site\packages\ifcopenshell\api"
|
||||
$ rd \S \Q "\path\to\blender\X.XX\scripts\addons\blenderbim\libs\site\packages\ifcopenshell\util"
|
||||
|
||||
# Replace them with links to the Git repository
|
||||
$ mklink \D "\path\to\blender\X.XX\scripts\addons\blenderbim\libs\site\packages\ifcopenshell\api" "src\ifcopenshell-python\ifcopenshell\api"
|
||||
$ mklink \D "\path\to\blender\X.XX\scripts\addons\blenderbim\libs\site\packages\ifcopenshell\util" "src\ifcopenshell-python\ifcopenshell\util"
|
||||
|
||||
|
||||
After you modify your code in the Git repository, you will need to restart
|
||||
Blender for the changes to take effect. In ``Edit > Preferences > Add-ons`` you
|
||||
will see that the version number of the BlenderBIM Add-on has changed to
|
||||
``0.0.999999``, which represents an un-versioned BlenderBIM Add-on.
|
||||
|
||||
Distro installation
|
||||
-------------------
|
||||
|
||||
Those on Arch Linux can check out this `AUR package <https://aur.archlinux.org/packages/ifcopenshell-git/>`__.
|
||||
|
||||
Tips for package managers
|
||||
-------------------------
|
||||
|
||||
If you are interested in packaging the BlenderBIM Add-on for a packaging
|
||||
manager, read on.
|
||||
|
||||
The BlenderBIM Add-on is fully contained in the ``blenderbim/`` subfolder of the
|
||||
Blender add-ons directory. This is typically distributed as a zipfile as per
|
||||
@@ -58,9 +162,8 @@ This corresponds to the structure found in the source code `here
|
||||
<https://github.com/IfcOpenShell/IfcOpenShell/tree/v0.6.0/src/blenderbim/blenderbim>`__.
|
||||
|
||||
The BlenderBIM Add-on is complex, and requires many dependencies, including
|
||||
Python modules, binaries, and static assets. These dependencies are bundled with
|
||||
the add-on for convenience in the **Packaged installation** and **Daily build
|
||||
installation** methods.
|
||||
Python modules, binaries, and static assets. When packaged for users, these
|
||||
dependencies are bundled with the add-on for convenience.
|
||||
|
||||
If you choose to install the BlenderBIM Add-on and use your own system
|
||||
dependencies, the source of truth for how dependencies are bundled are found in
|
||||
@@ -92,7 +195,7 @@ Required Python modules to be stored in ``libs/site/packages/`` are:
|
||||
elementpath
|
||||
six
|
||||
lark-parser
|
||||
fcl
|
||||
hppfcl
|
||||
behave
|
||||
parse
|
||||
parse_type
|
||||
@@ -108,14 +211,8 @@ Required Python modules to be stored in ``libs/site/packages/`` are:
|
||||
Notes:
|
||||
|
||||
1. ``ifcopenshell`` almost always requires the latest version due to the fast paced nature of the add-on development.
|
||||
2. ``fcl`` is not bundled for MacOS, due to lack of maintained community build. This is required for clash detection.
|
||||
3. ``behave`` requires `patches <https://github.com/IfcOpenShell/IfcOpenShell/tree/v0.6.0/src/ifcbimtester/patch>`__.
|
||||
4. ``ifcjson`` can be found `here <https://github.com/IFCJSON-Team/IFC2JSON_python/tree/master/file_converters>`__.
|
||||
|
||||
Required binaries are:
|
||||
::
|
||||
|
||||
libs/IfcConvert
|
||||
2. ``behave`` requires `patches <https://github.com/IfcOpenShell/IfcOpenShell/tree/v0.6.0/src/ifcbimtester/patch>`__.
|
||||
3. ``ifcjson`` can be found `here <https://github.com/IFCJSON-Team/IFC2JSON_python/tree/master/file_converters>`__.
|
||||
|
||||
Required static assets are:
|
||||
::
|
||||
@@ -123,121 +220,31 @@ Required static assets are:
|
||||
bim/data/gantt/jsgantt.js (from jsgantt-improved)
|
||||
bim/data/gantt/jsgantt.css (from jsgantt-improved)
|
||||
|
||||
Distro installation
|
||||
-------------------
|
||||
|
||||
Those on Arch Linux can check out this `AUR package <https://aur.archlinux.org/packages/ifcopenshell-git/>`__.
|
||||
|
||||
Source installation
|
||||
-------------------
|
||||
|
||||
It is possible to run the latest bleeding edge version of BlenderBIM without
|
||||
having to wait for an official release, since BlenderBIM is coded in Python and
|
||||
doesn't require any compilation.
|
||||
|
||||
Note that the BlenderBIM Add-on does depend on IfcOpenShell, and IfcOpenShell
|
||||
does require compilation. The following instructions will use a pre-built
|
||||
IfcOpenShell (using an IfcOpenBot build) for convenience. Instructions on how to
|
||||
compile IfcOpenShell is out of scope of this document.
|
||||
|
||||
You can create your own package by using the Makefile as shown below. You can
|
||||
choose between a ``PLATFORM`` of ``linux``, ``macos``, and ``win``. You can
|
||||
choose between a ``PYVERSION`` of ``py39`` and ``py37``.
|
||||
::
|
||||
|
||||
$ cd src/blenderbim
|
||||
$ make dist PLATFORM=linux PYVERSION=py39
|
||||
$ ls dist/
|
||||
|
||||
However, creating a build, uninstalling the old add-on, and installing a new
|
||||
build is a slow process. You can do this, but we do not recommend it. A more
|
||||
rapid approach is to follow the **Daily build installation** method, as this
|
||||
provides all dependencies for you out of the box. Then, we can replace certain
|
||||
Python files that tend to be updated frequently with those from the Git
|
||||
repository. We're going to use symlinks (Windows users can use ``mklink``), so
|
||||
we can code in our Git repository, and see the changes in our Blender
|
||||
installation.
|
||||
|
||||
In addition, we're also going to replace the Python code of the IfcOpenShell
|
||||
dependency with our Git repository, since most of the BlenderBIM Add-on
|
||||
functionality is agnostic of Blender, and is actually part of IfcOpenShell.
|
||||
Therefore, we need to keep this dependency highly updated as well.
|
||||
|
||||
The downside with this approach is that if a new dependency is added, or a
|
||||
compiled dependency version requirement has changed, or the build system
|
||||
changes, you'll need to fix your setup manually. But this is relatively rare.
|
||||
|
||||
::
|
||||
|
||||
$ git clone https://github.com/IfcOpenShell/IfcOpenShell.git
|
||||
$ cd IfcOpenShell
|
||||
|
||||
# Remove the Blender add-on Python code
|
||||
$ rm -r /path/to/blender/2.XX/scripts/addons/blenderbim/bim/
|
||||
|
||||
# Replace them with links to the Git repository
|
||||
$ ln -s src/blenderbim/blenderbim/bim /path/to/blender/2.XX/scripts/addons/blenderbim/bim
|
||||
|
||||
# Remove the IfcOpenShell dependency Python code
|
||||
$ rm -r /path/to/blender/2.XX/scripts/addons/blenderbim/libs/site/packages/ifcopenshell/api
|
||||
$ rm -r /path/to/blender/2.XX/scripts/addons/blenderbim/libs/site/packages/ifcopenshell/util
|
||||
|
||||
# Replace them with links to the Git repository
|
||||
$ ln -s src/ifcopenshell-python/ifcopenshell/api /path/to/blender/2.XX/scripts/addons/blenderbim/libs/site/packages/ifcopenshell/api
|
||||
$ ln -s src/ifcopenshell-python/ifcopenshell/util /path/to/blender/2.XX/scripts/addons/blenderbim/libs/site/packages/ifcopenshell/util
|
||||
|
||||
On Windows:
|
||||
|
||||
::
|
||||
|
||||
$ git clone https://github.com/IfcOpenShell/IfcOpenShell.git
|
||||
$ cd IfcOpenShell
|
||||
|
||||
# Remove the Blender add-on Python code
|
||||
$ rd /S /Q "\path\to\blender\2.XX\scripts\addons\blenderbim\bim\"
|
||||
|
||||
# Replace them with links to the Git repository
|
||||
$ mklink /D "\path\to\blender\2.XX\scripts\addons\blenderbim\bim" "src\blenderbim\blenderbim\bim"
|
||||
|
||||
# Remove the IfcOpenShell dependency Python code
|
||||
$ rd \S \Q "\path\to\blender\2.XX\scripts\addons\blenderbim\libs\site\packages\ifcopenshell\api"
|
||||
$ rd \S \Q "\path\to\blender\2.XX\scripts\addons\blenderbim\libs\site\packages\ifcopenshell\util"
|
||||
|
||||
# Replace them with links to the Git repository
|
||||
$ mklink \D "\path\to\blender\2.XX\scripts\addons\blenderbim\libs\site\packages\ifcopenshell\api" "src\ifcopenshell-python\ifcopenshell\api"
|
||||
$ mklink \D "\path\to\blender\2.XX\scripts\addons\blenderbim\libs\site\packages\ifcopenshell\util" "src\ifcopenshell-python\ifcopenshell\util"
|
||||
|
||||
|
||||
After you modify your code in the Git repository, you will need to restart
|
||||
Blender for the changes to take effect. In ``Edit > Preferences > Add-ons`` you
|
||||
will see that the version number of BlenderBIM has changed to ``0.0.999999``,
|
||||
which represents an un-versioned BlenderBIM.
|
||||
|
||||
Where is the BlenderBIM Add-on installed?
|
||||
-----------------------------------------
|
||||
|
||||
If you downloaded Blender as a ``.zip`` file without running an installer, you
|
||||
will find the BlenderBIM Add-on installed in the following directory, where
|
||||
``2.XX`` is the Blender version:
|
||||
``X.XX`` is the Blender version:
|
||||
::
|
||||
|
||||
/path/to/blender/2.XX/scripts/addons/
|
||||
/path/to/blender/X.XX/scripts/addons/
|
||||
|
||||
Otherwise, if you installed Blender using an installation package, the add-ons
|
||||
folder depends on which operating system you use. On Linux:
|
||||
::
|
||||
|
||||
~/.config/blender/2.XX/scripts/addons/
|
||||
~/.config/blender/X.XX/scripts/addons/
|
||||
|
||||
On Mac:
|
||||
::
|
||||
|
||||
/Users/{YOUR_USER}/Library/Application Support/Blender/2.XX/
|
||||
/Users/{YOUR_USER}/Library/Application Support/Blender/X.XX/
|
||||
|
||||
On Windows:
|
||||
::
|
||||
|
||||
C:\Users\{YOUR_USER}\AppData\Roaming\Blender Foundation\2.XX\scripts\addons
|
||||
C:\Users\{YOUR_USER}\AppData\Roaming\Blender Foundation\X.XX\scripts\addons
|
||||
|
||||
Upon installation, the BlenderBIM Add-on is stored in the ``blenderbim/``
|
||||
directory.
|
||||
@@ -264,8 +271,10 @@ FAQ
|
||||
|
||||
1. I get an error similar to "ImportError: IfcOpenShell not built for 'linux/64bit/python3.7'"
|
||||
|
||||
If you are using Blender <2.93, then you need to use a daily build. See the
|
||||
instructions above for a daily build installation.
|
||||
Check which BlenderBIM Add-on build you are using. The zip will have either
|
||||
``py37``, ``py39``, or ``py310`` in the name. See the instructions in the
|
||||
**Unstable installation** section to check that you have installed the correct
|
||||
version.
|
||||
|
||||
2. I am on Ubuntu and get an error similar to "ImportError: /lib/x86_64-linux-gnu/libm.so.6: version GLIBC_2.29 not found"
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
import bpy
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
import blenderbim.tool as tool
|
||||
|
||||
|
||||
class LibraryGenerator:
|
||||
|
||||
@@ -121,7 +121,7 @@ class TestOpenSheet:
|
||||
|
||||
class TestRemoveSheet:
|
||||
def test_run(self, ifc, drawing):
|
||||
ifc.run("document.remove_document", document="sheet").should_be_called()
|
||||
ifc.run("document.remove_information", information="sheet").should_be_called()
|
||||
drawing.import_sheets().should_be_called()
|
||||
subject.remove_sheet(ifc, drawing, sheet="sheet")
|
||||
|
||||
@@ -162,7 +162,16 @@ class TestAddDrawing:
|
||||
collector.assign("obj").should_be_called()
|
||||
ifc.run("pset.add_pset", product="element", name="EPset_Drawing").should_be_called().will_return("pset")
|
||||
ifc.run(
|
||||
"pset.edit_pset", pset="pset", properties={"TargetView": "target_view", "Scale": "1/100"}
|
||||
"pset.edit_pset",
|
||||
pset="pset",
|
||||
properties={
|
||||
"TargetView": "target_view",
|
||||
"Scale": "1/100",
|
||||
"HumanScale": "1:100",
|
||||
"HasUnderlay": False,
|
||||
"HasLinework": True,
|
||||
"HasAnnotation": True,
|
||||
},
|
||||
).should_be_called()
|
||||
drawing.import_drawings().should_be_called()
|
||||
subject.add_drawing(ifc, collector, drawing, target_view="target_view", location_hint="location_hint")
|
||||
|
||||
@@ -28,6 +28,7 @@ class TestCopyClass:
|
||||
|
||||
def test_copy_with_new_geometry_derived_from_the_type(self, ifc, collector, root):
|
||||
ifc.get_entity("obj").should_be_called().will_return("original_element")
|
||||
root.get_object_representation("obj").should_be_called().will_return("representation")
|
||||
ifc.run("root.copy_class", product="original_element").should_be_called().will_return("element")
|
||||
ifc.link("element", "obj").should_be_called()
|
||||
root.get_element_type("element").should_be_called().will_return("type")
|
||||
@@ -41,12 +42,12 @@ class TestCopyClass:
|
||||
|
||||
def test_copy_with_new_geometry_added_afresh_for_speed(self, ifc, collector, geometry, root):
|
||||
ifc.get_entity("obj").should_be_called().will_return("original_element")
|
||||
root.get_object_representation("obj").should_be_called().will_return("representation")
|
||||
ifc.run("root.copy_class", product="original_element").should_be_called().will_return("element")
|
||||
ifc.link("element", "obj").should_be_called()
|
||||
root.get_element_type("element").should_be_called().will_return("type")
|
||||
root.does_type_have_representations("type").should_be_called().will_return(False)
|
||||
|
||||
root.get_object_representation("obj").should_be_called().will_return("representation")
|
||||
root.get_representation_context("representation").should_be_called().will_return("context")
|
||||
geometry.get_ifc_representation_class("element", "representation").should_be_called().will_return(
|
||||
"ifc_representation_class"
|
||||
@@ -64,17 +65,18 @@ class TestCopyClass:
|
||||
|
||||
def test_copy_with_no_new_geometry(self, ifc, collector, geometry, root):
|
||||
ifc.get_entity("obj").should_be_called().will_return("original_element")
|
||||
root.get_object_representation("obj").should_be_called().will_return(None)
|
||||
ifc.run("root.copy_class", product="original_element").should_be_called().will_return("element")
|
||||
ifc.link("element", "obj").should_be_called()
|
||||
root.get_element_type("element").should_be_called().will_return("type")
|
||||
root.does_type_have_representations("type").should_be_called().will_return(False)
|
||||
root.get_object_representation("obj").should_be_called().will_return(None)
|
||||
collector.assign("obj").should_be_called()
|
||||
root.is_opening_element("element").should_be_called().will_return(False)
|
||||
subject.copy_class(ifc, collector, geometry, root, obj="obj")
|
||||
|
||||
def test_copied_openings_have_dynamic_voids_added(self, ifc, collector, root):
|
||||
ifc.get_entity("obj").should_be_called().will_return("original_element")
|
||||
root.get_object_representation("obj").should_be_called().will_return(None)
|
||||
ifc.run("root.copy_class", product="original_element").should_be_called().will_return("element")
|
||||
ifc.link("element", "obj").should_be_called()
|
||||
root.get_element_type("element").should_be_called().will_return("type")
|
||||
|
||||
@@ -111,6 +111,18 @@ public:
|
||||
std::map<int, TopoDS_Shape> Shape;
|
||||
};
|
||||
|
||||
namespace util {
|
||||
template <typename T>
|
||||
typename std::enable_if<std::is_pointer<T>::value, T&>::type conditional_address_of(T& t) {
|
||||
return t;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
typename std::enable_if<!std::is_pointer<T>::value, T*>::type conditional_address_of(T& t) {
|
||||
return &t;
|
||||
}
|
||||
}
|
||||
|
||||
class IFC_GEOM_API MAKE_TYPE_NAME(Kernel) : public IfcGeom::Kernel {
|
||||
private:
|
||||
|
||||
@@ -126,7 +138,7 @@ private:
|
||||
class faceset_helper {
|
||||
private:
|
||||
MAKE_TYPE_NAME(Kernel)* kernel_;
|
||||
std::set<LP> duplicates_;
|
||||
std::set<typename std::conditional<std::is_pointer<LP>::value, LP, const LP*>::type> duplicates_;
|
||||
std::map<const void*, int> vertex_mapping_;
|
||||
std::map<std::pair<int, int>, TopoDS_Edge> edges_;
|
||||
// not always in use
|
||||
@@ -225,8 +237,8 @@ private:
|
||||
return true;
|
||||
}
|
||||
|
||||
bool wire(LP loop, TopoDS_Wire& wire) {
|
||||
if (duplicates_.find(loop) != duplicates_.end()) {
|
||||
bool wire(const LP& loop, TopoDS_Wire& wire) {
|
||||
if (duplicates_.find(util::conditional_address_of(loop)) != duplicates_.end()) {
|
||||
return false;
|
||||
}
|
||||
BRep_Builder builder;
|
||||
|
||||
@@ -4599,6 +4599,13 @@ namespace {
|
||||
|
||||
if (outer_wire.IsNull() || !it2.Value().IsSame(outer_wire)) {
|
||||
wires.push_back(TopoDS::Wire(it2.Value()));
|
||||
|
||||
if (shape_index == 0 && num_wires > 0) {
|
||||
// An inner wire on the first operand face: reverse, because
|
||||
// MakeFace expects inner boundaries to be added as bounded
|
||||
// areas.
|
||||
wires.back().Reverse();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5606,7 +5613,7 @@ IfcGeom::Kernel::faceset_helper<CP, LP>::faceset_helper(
|
||||
|
||||
if (edge_sets.find(segment_set) != edge_sets.end()) {
|
||||
duplicate_faces++;
|
||||
duplicates_.insert(*ps);
|
||||
duplicates_.insert(util::conditional_address_of(*ps));
|
||||
continue;
|
||||
}
|
||||
edge_sets.insert(segment_set);
|
||||
@@ -5639,8 +5646,8 @@ IfcGeom::Kernel::faceset_helper<CP, LP>::faceset_helper(
|
||||
}
|
||||
}
|
||||
|
||||
if (loops_removed || (non_manifold && should_be_closed)) {
|
||||
Logger::Warning(boost::lexical_cast<std::string>(duplicate_faces) + " duplicate faces removed, " + boost::lexical_cast<std::string>(loops_removed) + " loops removed and " + boost::lexical_cast<std::string>(non_manifold) + " non-manifold edges");
|
||||
if (duplicates_.size() || loops_removed || (non_manifold && should_be_closed)) {
|
||||
Logger::Warning(boost::lexical_cast<std::string>(duplicate_faces) + " duplicate faces removed, " + boost::lexical_cast<std::string>(loops_removed) + " degenerate loops eliminated and " + boost::lexical_cast<std::string>(non_manifold) + " non-manifold edges");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -92,7 +92,12 @@
|
||||
#include <TopTools_ListIteratorOfListOfShape.hxx>
|
||||
|
||||
#include <BRepAdaptor_CompCurve.hxx>
|
||||
|
||||
#include <Standard_Version.hxx>
|
||||
#if OCC_VERSION_HEX < 0x70600
|
||||
#include <BRepAdaptor_HCompCurve.hxx>
|
||||
#endif
|
||||
|
||||
#include <Approx_Curve3d.hxx>
|
||||
|
||||
#include "../ifcgeom/IfcGeom.h"
|
||||
@@ -483,8 +488,12 @@ bool IfcGeom::Kernel::convert(const IfcSchema::IfcTrimmedCurve* l, TopoDS_Wire&
|
||||
Logger::Warning("Approximating BasisCurve due to possible discontinuities", l);
|
||||
TopoDS_Wire w;
|
||||
if (!convert_wire(basis_curve, w)) return false;
|
||||
#if OCC_VERSION_HEX < 0x70600
|
||||
BRepAdaptor_CompCurve cc(w, true);
|
||||
Handle(Adaptor3d_HCurve) hcc = Handle(Adaptor3d_HCurve)(new BRepAdaptor_HCompCurve(cc));
|
||||
#else
|
||||
auto hcc = new BRepAdaptor_CompCurve(w, true);
|
||||
#endif
|
||||
// @todo, arbitrary numbers here, note they cannot be too high as contiguous memory is allocated based on them.
|
||||
Approx_Curve3d approx(hcc, getValue(GV_PRECISION), GeomAbs_C0, 10, 10);
|
||||
curve = approx.Curve();
|
||||
@@ -606,7 +615,10 @@ bool IfcGeom::Kernel::convert(const IfcSchema::IfcTrimmedCurve* l, TopoDS_Wire&
|
||||
}
|
||||
}
|
||||
|
||||
if (isConic && ALMOST_THE_SAME(fmod(flts[1]-flts[0],M_PI*2.), 0., 100 * getValue(GV_PRECISION) / (2 * M_PI * radius))) {
|
||||
// @todo is 100. not too much? Check with the original issue.
|
||||
const double precision_markup = getValue(IfcGeom::Kernel::GV_PRECISION_FACTOR) == 1. ? 1. : 100.;
|
||||
|
||||
if (isConic && ALMOST_THE_SAME(fmod(flts[1]-flts[0],M_PI*2.), 0., precision_markup * getValue(GV_PRECISION) / (2 * M_PI * radius))) {
|
||||
e = BRepBuilderAPI_MakeEdge(curve).Edge();
|
||||
} else {
|
||||
BRepBuilderAPI_MakeEdge me (curve,flts[0],flts[1]);
|
||||
|
||||
@@ -185,7 +185,7 @@ class Selector:
|
||||
elif token_type == "SIGNED_FLOAT":
|
||||
value = float(filter_rule.children[2].children[0])
|
||||
elif token_type == "BOOLEAN":
|
||||
value = filter_rule.children[2].children[0] == "TRUE"
|
||||
value = filter_rule.children[2].children[0].lower() == 'true'
|
||||
elif token_type == "NULL":
|
||||
value = None
|
||||
for element in elements:
|
||||
|
||||
@@ -36,7 +36,6 @@
|
||||
#include <TopoDS_Edge.hxx>
|
||||
#include <TopExp_Explorer.hxx>
|
||||
#include <BRep_Tool.hxx>
|
||||
#include <BRepAlgo_Section.hxx>
|
||||
#include <BRepTools.hxx>
|
||||
#include <BRepAlgoAPI_Section.hxx>
|
||||
#include <ShapeAnalysis_FreeBounds.hxx>
|
||||
|
||||