From adcf22f28199e1d42638b563153a5a31291f95ae Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Thu, 31 Aug 2023 12:40:27 +1000 Subject: [PATCH] Fix #3002. You can now have metadata classes on projection lines. --- .../blenderbim/bim/module/drawing/operator.py | 117 ++++++++++-------- 1 file changed, 66 insertions(+), 51 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/drawing/operator.py b/src/blenderbim/blenderbim/bim/module/drawing/operator.py index 6a25b87f8c..389d5958fa 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/operator.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/operator.py @@ -555,7 +555,7 @@ class CreateDrawing(bpy.types.Operator): if self.camera.data.BIMCameraProperties.calculate_shapely_surfaces: # shapely variant - group = root.findall(".//{http://www.w3.org/2000/svg}g")[0] + group = root.find("{http://www.w3.org/2000/svg}g") nm = group.attrib["{http://www.ifcopenshell.org/ns}name"] m4 = np.array(json.loads(group.attrib["{http://www.ifcopenshell.org/ns}plane"])) m3 = np.array(json.loads(group.attrib["{http://www.ifcopenshell.org/ns}matrix3"])) @@ -566,10 +566,10 @@ class CreateDrawing(bpy.types.Operator): m44[1][3] = m3[1][2] m44 = np.linalg.inv(m44) - projections = group.findall('.//{http://www.w3.org/2000/svg}g[@class="projection"]') or [] + projections = root.xpath(".//svg:g[contains(@class, 'projection')]", namespaces={'svg': 'http://www.w3.org/2000/svg'}) + boundary_lines = [] for projection in projections: - boundary_lines = [] for path in projection.findall("./{http://www.w3.org/2000/svg}path"): # Rounding is necessary to ensure coincident points are coincident start, end = [[round(float(o), 1) for o in co[1:].split(",")] for co in path.attrib["d"].split()] @@ -578,50 +578,57 @@ class CreateDrawing(bpy.types.Operator): # Extension by 0.5mm is necessary to ensure lines overlap with other diagonal lines start, end = tool.Drawing.extend_line(start, end, 0.5) boundary_lines.append(shapely.LineString([start, end])) - unioned_boundaries = shapely.union_all(shapely.GeometryCollection(boundary_lines)) - closed_polygons = shapely.polygonize(unioned_boundaries.geoms) - for polygon in closed_polygons.geoms: - # Less than 1mm2 is not worth styling on sheet - if polygon.area < 1: - continue - centroid = polygon.centroid - internal_point = centroid if polygon.contains(centroid) else polygon.representative_point() - if internal_point: - internal_point = [internal_point.x, internal_point.y] - a, b = self.drawing_to_model_co(m44, m4, internal_point, 0.0), self.drawing_to_model_co( - m44, m4, internal_point, -100.0 - ) - inside_elements = [e for e in tree.select(self.pythonize(a)) if not e.is_a("IfcAnnotation")] - if not inside_elements: - elements = [ - e - for e in tree.select_ray(self.pythonize(a), self.pythonize(b - a)) - if not e.instance.is_a("IfcAnnotation") - and tool.Cad.is_point_on_edge( - Vector(list(e.position)), (Vector(self.pythonize(a)), Vector(self.pythonize(b))) + unioned_boundaries = shapely.union_all(shapely.GeometryCollection(boundary_lines)) + closed_polygons = shapely.polygonize(unioned_boundaries.geoms) + + for polygon in closed_polygons.geoms: + # Less than 1mm2 is not worth styling on sheet + if polygon.area < 1: + continue + centroid = polygon.centroid + internal_point = centroid if polygon.contains(centroid) else polygon.representative_point() + if internal_point: + internal_point = [internal_point.x, internal_point.y] + a, b = self.drawing_to_model_co(m44, m4, internal_point, 0.0), self.drawing_to_model_co( + m44, m4, internal_point, -100.0 + ) + inside_elements = [e for e in tree.select(self.pythonize(a)) if not e.is_a("IfcAnnotation")] + if not inside_elements: + elements = [ + e + for e in tree.select_ray(self.pythonize(a), self.pythonize(b - a)) + if not e.instance.is_a("IfcAnnotation") + and tool.Cad.is_point_on_edge( + Vector(list(e.position)), (Vector(self.pythonize(a)), Vector(self.pythonize(b))) + ) + ] + if elements: + path = etree.Element("path") + d = ( + "M" + + " L".join( + [",".join([str(o) for o in co]) for co in polygon.exterior.coords[0:-1]] ) - ] - if elements: - path = etree.Element("path") - d = ( - "M" - + " L".join( - [",".join([str(o) for o in co]) for co in polygon.exterior.coords[0:-1]] - ) + + " Z" + ) + for interior in polygon.interiors: + d += ( + " M" + + " L".join([",".join([str(o) for o in co]) for co in interior.coords[0:-1]]) + " Z" ) - for interior in polygon.interiors: - d += ( - " M" - + " L".join([",".join([str(o) for o in co]) for co in interior.coords[0:-1]]) - + " Z" - ) - path.attrib["d"] = d - classes = self.get_svg_classes(ifc.by_id(elements[0].instance.id())) - classes.append("surface") - path.set("class", " ".join(list(classes))) - group.insert(0, path) + path.attrib["d"] = d + classes = self.get_svg_classes(ifc.by_id(elements[0].instance.id())) + classes.append(f"intpoint-{internal_point}") + classes.append(f"ab-{a}, {b}") + for i, ray_result in enumerate(elements): + classes.append(f"el{i}-{ray_result.instance.id()}") + classes.append(f"el{i}-pos-{list(ray_result.position)}") + classes.append(f"el{i}-dst-{ray_result.distance}") + classes.append("surface") + path.set("class", " ".join(list(classes))) + group.insert(0, path) if self.camera.data.BIMCameraProperties.calculate_svgfill_surfaces: results = etree.tostring(root).decode("utf8") @@ -636,7 +643,7 @@ class CreateDrawing(bpy.types.Operator): dom1 = parseString(svg_data_1) svg1 = dom1.childNodes[0] - groups1 = [g for g in yield_groups(svg1) if g.getAttribute("class") == "projection"] + groups1 = [g for g in yield_groups(svg1) if "projection" in g.getAttribute("class")] ls_groups = ifcopenshell.ifcopenshell_wrapper.svg_to_line_segments(results, "projection") @@ -854,6 +861,7 @@ class CreateDrawing(bpy.types.Operator): self.serialiser.setSubtractionSettings(ifcopenshell.ifcopenshell_wrapper.ALWAYS) self.serialiser.setUsePrefiltering(True) # See #3359 self.serialiser.setUnifyInputs(True) + self.serialiser.setSegmentProjection(True) if target_view == "REFLECTED_PLAN_VIEW": self.serialiser.setMirrorY(True) # tree = ifcopenshell.geom.tree() @@ -913,7 +921,7 @@ class CreateDrawing(bpy.types.Operator): # Drawing convention states that same objects classes with the same material are merged when cut. join_criteria = ["class", "material.Name", 'r"Pset.*Common"."Status"'] - group = root.findall(".//{http://www.w3.org/2000/svg}g")[0] + group = root.find("{http://www.w3.org/2000/svg}g") joined_paths = {} self.is_manifold_cache = {} @@ -921,9 +929,15 @@ class CreateDrawing(bpy.types.Operator): for el in root.findall(".//{http://www.w3.org/2000/svg}g[@{http://www.ifcopenshell.org/ns}guid]"): element = ifc.by_guid(el.get("{http://www.ifcopenshell.org/ns}guid")) - classes = self.get_svg_classes(element) - classes.append("cut") - el.set("class", " ".join(classes)) + if "projection" in el.get("class", "").split(): + classes = self.get_svg_classes(element) + classes.append("projection") + el.set("class", " ".join(classes)) + continue + else: + classes = self.get_svg_classes(element) + classes.append("cut") + el.set("class", " ".join(classes)) obj = tool.Ifc.get_object(element) if not self.is_manifold(obj): @@ -1045,9 +1059,10 @@ class CreateDrawing(bpy.types.Operator): # IfcConvert puts the projection afterwards which is not correct since # projection should be drawn underneath the cut. group = root.find("{http://www.w3.org/2000/svg}g") - projection = group.find("{http://www.w3.org/2000/svg}g[@class='projection']") - projection.getparent().remove(projection) - group.insert(0, projection) + projections = root.xpath(".//svg:g[contains(@class, 'projection')]", namespaces={'svg': 'http://www.w3.org/2000/svg'}) + for projection in projections: + projection.getparent().remove(projection) + group.insert(0, projection) def generate_annotation(self, context): if not ifcopenshell.util.element.get_pset(self.drawing, "EPset_Drawing", "HasAnnotation"):