@@ -485,10 +485,13 @@ def generate_linework(self, context):
485485 self .serialiser .finalize ()
486486 results = self .svg_buffer .get_value ()
487487
488+ root = etree .fromstring (results )
489+ self .move_projection_to_bottom (root )
490+ self .merge_linework_and_add_metadata (root )
491+
488492 if self .camera .data .BIMCameraProperties .calculate_shapely_surfaces :
489493 # shapely variant
490- root = etree .fromstring (results )
491- group = root .findall ('.//{http://www.w3.org/2000/svg}g[@{http://www.ifcopenshell.org/ns}name]' )[0 ]
494+ group = root .findall (".//{http://www.w3.org/2000/svg}g" )[0 ]
492495 nm = group .attrib ["{http://www.ifcopenshell.org/ns}name" ]
493496 m4 = np .array (json .loads (group .attrib ["{http://www.ifcopenshell.org/ns}plane" ]))
494497 m3 = np .array (json .loads (group .attrib ["{http://www.ifcopenshell.org/ns}matrix3" ]))
@@ -503,7 +506,7 @@ def generate_linework(self, context):
503506
504507 for projection in projections :
505508 boundary_lines = []
506- for path in projection .findall (' ./{http://www.w3.org/2000/svg}path' ):
509+ for path in projection .findall (" ./{http://www.w3.org/2000/svg}path" ):
507510 start , end = [co [1 :].split ("," ) for co in path .attrib ["d" ].split ()]
508511 boundary_lines .append (shapely .LineString ([start , end ]))
509512 unioned_boundaries = shapely .union_all (shapely .GeometryCollection (boundary_lines ))
@@ -517,23 +520,39 @@ def generate_linework(self, context):
517520 internal_point = centroid if polygon .contains (centroid ) else polygon .representative_point ()
518521 if internal_point :
519522 internal_point = [internal_point .x , internal_point .y ]
520- a , b = self .drawing_to_model_co (m44 , m4 , internal_point , 0.0 ), self .drawing_to_model_co (m44 , m4 , internal_point , - 100.0 )
523+ a , b = self .drawing_to_model_co (m44 , m4 , internal_point , 0.0 ), self .drawing_to_model_co (
524+ m44 , m4 , internal_point , - 100.0
525+ )
521526 inside_elements = [e for e in tree .select (self .pythonize (a )) if not e .is_a ("IfcAnnotation" )]
522527 if not inside_elements :
523- elements = [e for e in tree .select_ray (self .pythonize (a ), self .pythonize (b - a )) if not e .instance .is_a ("IfcAnnotation" )]
528+ elements = [
529+ e
530+ for e in tree .select_ray (self .pythonize (a ), self .pythonize (b - a ))
531+ if not e .instance .is_a ("IfcAnnotation" )
532+ ]
524533 if elements :
525- path = etree .SubElement (group , "path" )
526- d = "M" + " L" .join (["," .join ([str (o ) for o in co ]) for co in polygon .exterior .coords [0 :- 1 ]]) + " Z"
534+ path = etree .Element ("path" )
535+ d = (
536+ "M"
537+ + " L" .join (
538+ ["," .join ([str (o ) for o in co ]) for co in polygon .exterior .coords [0 :- 1 ]]
539+ )
540+ + " Z"
541+ )
527542 for interior in polygon .interiors :
528- d += " M" + " L" .join (["," .join ([str (o ) for o in co ]) for co in interior .coords [0 :- 1 ]]) + " Z"
543+ d += (
544+ " M"
545+ + " L" .join (["," .join ([str (o ) for o in co ]) for co in interior .coords [0 :- 1 ]])
546+ + " Z"
547+ )
529548 path .attrib ["d" ] = d
530549 classes = self .get_svg_classes (ifc .by_id (elements [0 ].instance .id ()))
531550 classes .append ("surface" )
532551 path .set ("class" , " " .join (list (classes )))
533-
534- results = etree .tostring (root )
552+ group .insert (0 , path )
535553
536554 if self .camera .data .BIMCameraProperties .calculate_svgfill_surfaces :
555+ results = etree .tostring (root ).decode ("utf8" )
537556 svg_data_1 = results
538557 from xml .dom .minidom import parseString
539558
@@ -624,7 +643,11 @@ def yield_groups(n):
624643 if iteration != num_passes :
625644 semantics [pi ] = (inside_elements [0 ], - 1 )
626645 else :
627- elements = [e for e in tree .select_ray (self .pythonize (a ), self .pythonize (b - a )) if not e .instance .is_a ("IfcAnnotation" )]
646+ elements = [
647+ e
648+ for e in tree .select_ray (self .pythonize (a ), self .pythonize (b - a ))
649+ if not e .instance .is_a ("IfcAnnotation" )
650+ ]
628651
629652 if elements :
630653 classes = self .get_svg_classes (ifc .by_id (elements [0 ].instance .id ()))
@@ -682,16 +705,56 @@ def format(x):
682705 # This generally shouldn't happen
683706 g1 .appendChild (g2 )
684707
685- data = dom1 .toxml ()
686- data = data .encode ("ascii" , "xmlcharrefreplace" )
687-
688- results = data
689- with profile ("Post processing" ):
708+ results = dom1 .toxml ()
709+ results = results .encode ("ascii" , "xmlcharrefreplace" )
690710 root = etree .fromstring (results )
691- self .move_projection_to_bottom (root )
692- self .merge_linework_and_add_metadata (root )
693- with open (svg_path , "wb" ) as svg :
694- svg .write (etree .tostring (root ))
711+
712+ # Spaces are handled as a special case, since they are often overlayed
713+ # in addition to elements. For example, a space should not obscure
714+ # other elements in projection. Spaces should also not override cut
715+ # elements but instead be drawn in addition to cut elements.
716+ spaces = tool .Drawing .get_drawing_spaces (self .camera_element )
717+
718+ group = root .findall (".//{http://www.w3.org/2000/svg}g" )[0 ]
719+
720+ self .svg_writer .calculate_scale ()
721+ x_offset = self .svg_writer .raw_width / 2
722+ y_offset = self .svg_writer .raw_height / 2
723+
724+ for space in spaces :
725+ obj = tool .Ifc .get_object (space )
726+ if not obj or not tool .Drawing .is_intersecting_camera (obj , self .camera ):
727+ continue
728+ verts , edges = tool .Drawing .bisect_mesh (obj , self .camera )
729+ verts = [self .svg_writer .project_point_onto_camera (Vector (v )) for v in verts ]
730+ line_strings = [
731+ shapely .LineString (
732+ [
733+ (
734+ (x_offset + verts [e [0 ]][0 ]) * self .svg_writer .svg_scale ,
735+ (y_offset - verts [e [0 ]][1 ]) * self .svg_writer .svg_scale ,
736+ ),
737+ (
738+ (x_offset + verts [e [1 ]][0 ]) * self .svg_writer .svg_scale ,
739+ (y_offset - verts [e [1 ]][1 ]) * self .svg_writer .svg_scale ,
740+ ),
741+ ]
742+ )
743+ for e in edges
744+ ]
745+ closed_polygons = shapely .polygonize (line_strings )
746+ for polygon in closed_polygons .geoms :
747+ classes = self .get_svg_classes (space )
748+ path = etree .Element ("path" )
749+ d = "M" + " L" .join (["," .join ([str (o ) for o in co ]) for co in polygon .exterior .coords [0 :- 1 ]]) + " Z"
750+ for interior in polygon .interiors :
751+ d += " M" + " L" .join (["," .join ([str (o ) for o in co ]) for co in interior .coords [0 :- 1 ]]) + " Z"
752+ path .attrib ["d" ] = d
753+ path .set ("class" , " " .join (list (classes )))
754+ group .append (path )
755+
756+ with open (svg_path , "wb" ) as svg :
757+ svg .write (etree .tostring (root ))
695758
696759 return svg_path
697760
@@ -748,13 +811,12 @@ def get_svg_classes(self, element):
748811 value = ifcopenshell .util .selector .get_element_value (element , key )
749812 if value :
750813 classes .append (
751- tool .Drawing .canonicalise_class_name (key )
752- + "-"
753- + tool .Drawing .canonicalise_class_name (str (value ))
814+ tool .Drawing .canonicalise_class_name (key ) + "-" + tool .Drawing .canonicalise_class_name (str (value ))
754815 )
755816 return classes
756817
757818 def merge_linework_and_add_metadata (self , root ):
819+ group = root .findall (".//{http://www.w3.org/2000/svg}g" )[0 ]
758820 joined_paths = {}
759821
760822 ifc = tool .Ifc .get ()
@@ -796,16 +858,15 @@ def merge_linework_and_add_metadata(self, root):
796858 elif type (merged_polygons ) == shapely .Polygon :
797859 merged_polygons = [merged_polygons ]
798860
799- root_g = root .findall (".//{http://www.w3.org/2000/svg}g" )[0 ]
800861 for polygon in merged_polygons :
801- g = etree .SubElement ( root , "g" )
862+ g = etree .Element ( "g" )
802863 path = etree .SubElement (g , "path" )
803864 d = "M" + " L" .join (["," .join ([str (o ) for o in co ]) for co in polygon .exterior .coords [0 :- 1 ]]) + " Z"
804865 for interior in polygon .interiors :
805866 d += " M" + " L" .join (["," .join ([str (o ) for o in co ]) for co in interior .coords [0 :- 1 ]]) + " Z"
806867 path .attrib ["d" ] = d
807868 g .set ("class" , " " .join (list (classes )))
808- root_g .append (g )
869+ group .append (g )
809870
810871 def drawing_to_model_co (self , m44 , m4 , xy , z = 0.0 ):
811872 xyzw = m44 @ np .array (xy + [z , 1.0 ])
@@ -816,14 +877,12 @@ def pythonize(self, arr):
816877 return tuple (map (float , arr ))
817878
818879 def move_projection_to_bottom (self , root ):
819- # https://stackoverflow.com/questions/36018627/sorting-child-elements-with-lxml-based-on-attribute-value
880+ # IfcConvert puts the projection afterwards which is not correct since
881+ # projection should be drawn underneath the cut.
820882 group = root .find ("{http://www.w3.org/2000/svg}g" )
821- if self .camera .data .BIMCameraProperties .calculate_svgfill_surfaces :
822- # SVGFill already places the projection in the right spot.
823- return
824- # group[:] = sorted(group, key=lambda e : "projection" in e.get("class"))
825- if group is not None :
826- group [:] = reversed (group )
883+ projection = group .find ("{http://www.w3.org/2000/svg}g[@class='projection']" )
884+ projection .getparent ().remove (projection )
885+ group .insert (0 , projection )
827886
828887 def generate_annotation (self , context ):
829888 if not self .cprops .has_annotation :
0 commit comments