mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-20 15:08:51 +00:00
See #3002. Spaces are now managed separately so that they don't conflict with projection polygon merging and raycasting.
This commit is contained in:
@@ -22,6 +22,7 @@
|
|||||||
text, tspan { /* 2.5mm */ fill: black; stroke: none; font-family: 'OpenGost Type B TT', 'DejaVu Sans Condensed', 'Liberation Sans', 'Arial Narrow', 'Arial'; font-size: 4.13px; }
|
text, tspan { /* 2.5mm */ fill: black; stroke: none; font-family: 'OpenGost Type B TT', 'DejaVu Sans Condensed', 'Liberation Sans', 'Arial Narrow', 'Arial'; font-size: 4.13px; }
|
||||||
.cut { fill: black; stroke: black; stroke-linecap: 'round'; stroke-width: 0.35; fill-rule: evenodd; }
|
.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; }
|
.projection { fill: white; stroke: black; stroke-linecap: 'round'; stroke-width: 0.25; }
|
||||||
|
.surface { stroke: none; fill: #fff; fill-rule: evenodd; }
|
||||||
.annotation { fill: none; stroke: black; stroke-linecap: 'round'; stroke-width: 0.25; }
|
.annotation { fill: none; stroke: black; stroke-linecap: 'round'; stroke-width: 0.25; }
|
||||||
.IfcAnnotation { fill: none; stroke: black; stroke-linecap: 'round'; stroke-width: 0.25; }
|
.IfcAnnotation { fill: none; stroke: black; stroke-linecap: 'round'; stroke-width: 0.25; }
|
||||||
.IfcGeographicElement { fill: none; stroke: black; stroke-linecap: 'round'; stroke-width: 1; }
|
.IfcGeographicElement { fill: none; stroke: black; stroke-linecap: 'round'; stroke-width: 1; }
|
||||||
|
|||||||
@@ -1975,79 +1975,15 @@ class CutDecorator:
|
|||||||
if verts is False:
|
if verts is False:
|
||||||
return None, None
|
return None, None
|
||||||
|
|
||||||
if not self.is_intersecting_camera(obj, context.scene.camera):
|
if not tool.Drawing.is_intersecting_camera(obj, context.scene.camera):
|
||||||
DecoratorData.cut_cache[element.id()] = (False, False)
|
DecoratorData.cut_cache[element.id()] = (False, False)
|
||||||
return None, None
|
return None, None
|
||||||
|
|
||||||
if verts is None:
|
if verts is None:
|
||||||
verts, edges = self.bisect_mesh(obj, context.scene.camera)
|
verts, edges = tool.Drawing.bisect_mesh(obj, context.scene.camera)
|
||||||
DecoratorData.cut_cache[element.id()] = (verts, edges)
|
DecoratorData.cut_cache[element.id()] = (verts, edges)
|
||||||
return verts, edges
|
return verts, edges
|
||||||
|
|
||||||
def is_intersecting_camera(self, obj, camera):
|
|
||||||
# Based on separating axis theorem
|
|
||||||
plane_co = camera.matrix_world.translation
|
|
||||||
plane_no = camera.matrix_world.col[2].xyz
|
|
||||||
|
|
||||||
# Broadphase check using the bounding box
|
|
||||||
bounding_box_world_coords = [obj.matrix_world @ Vector(coord) for coord in obj.bound_box]
|
|
||||||
bounding_box_signed_distances = [plane_no.dot(v - plane_co) for v in bounding_box_world_coords]
|
|
||||||
|
|
||||||
pos_exists_bb = any(d > 0 for d in bounding_box_signed_distances)
|
|
||||||
neg_exists_bb = any(d < 0 for d in bounding_box_signed_distances)
|
|
||||||
|
|
||||||
if not (pos_exists_bb and neg_exists_bb):
|
|
||||||
return False
|
|
||||||
|
|
||||||
bm = bmesh.new()
|
|
||||||
bm.from_mesh(obj.data)
|
|
||||||
|
|
||||||
# Transform the vertices to world space
|
|
||||||
mesh_mat = obj.matrix_world
|
|
||||||
bm.transform(mesh_mat)
|
|
||||||
|
|
||||||
# Calculate the signed distances of all vertices from the plane
|
|
||||||
signed_distances = [plane_no.dot(v.co - plane_co) for v in bm.verts]
|
|
||||||
|
|
||||||
bm.free()
|
|
||||||
|
|
||||||
# Check for intersection
|
|
||||||
pos_exists = any(d > 0 for d in signed_distances)
|
|
||||||
neg_exists = any(d < 0 for d in signed_distances)
|
|
||||||
|
|
||||||
return pos_exists and neg_exists
|
|
||||||
|
|
||||||
def bisect_mesh(self, obj, camera):
|
|
||||||
camera_matrix = obj.matrix_world.inverted() @ camera.matrix_world
|
|
||||||
plane_co = camera_matrix.translation
|
|
||||||
plane_no = camera_matrix.col[2].xyz
|
|
||||||
|
|
||||||
global_offset = camera.matrix_world.col[2].xyz * -camera.data.clip_start
|
|
||||||
|
|
||||||
bm = bmesh.new()
|
|
||||||
bm.from_mesh(obj.data)
|
|
||||||
|
|
||||||
# Run the bisect operation
|
|
||||||
geom = bm.verts[:] + bm.edges[:] + bm.faces[:]
|
|
||||||
results = bmesh.ops.bisect_plane(bm, geom=geom, dist=0.0001, plane_co=plane_co, plane_no=plane_no)
|
|
||||||
|
|
||||||
vert_map = {}
|
|
||||||
verts = []
|
|
||||||
edges = []
|
|
||||||
i = 0
|
|
||||||
for geom in results["geom_cut"]:
|
|
||||||
if isinstance(geom, bmesh.types.BMVert):
|
|
||||||
verts.append(tuple((obj.matrix_world @ geom.co) + global_offset))
|
|
||||||
vert_map[geom.index] = i
|
|
||||||
i += 1
|
|
||||||
else:
|
|
||||||
# It seems as though edges always appear after verts
|
|
||||||
edges.append([vert_map[v.index] for v in geom.verts])
|
|
||||||
|
|
||||||
bm.free()
|
|
||||||
|
|
||||||
return verts, edges
|
|
||||||
|
|
||||||
|
|
||||||
class DecorationsHandler:
|
class DecorationsHandler:
|
||||||
decorators_classes = [
|
decorators_classes = [
|
||||||
|
|||||||
@@ -485,10 +485,13 @@ class CreateDrawing(bpy.types.Operator):
|
|||||||
self.serialiser.finalize()
|
self.serialiser.finalize()
|
||||||
results = self.svg_buffer.get_value()
|
results = self.svg_buffer.get_value()
|
||||||
|
|
||||||
|
root = etree.fromstring(results)
|
||||||
|
self.move_projection_to_bottom(root)
|
||||||
|
self.merge_linework_and_add_metadata(root)
|
||||||
|
|
||||||
if self.camera.data.BIMCameraProperties.calculate_shapely_surfaces:
|
if self.camera.data.BIMCameraProperties.calculate_shapely_surfaces:
|
||||||
# shapely variant
|
# shapely variant
|
||||||
root = etree.fromstring(results)
|
group = root.findall(".//{http://www.w3.org/2000/svg}g")[0]
|
||||||
group = root.findall('.//{http://www.w3.org/2000/svg}g[@{http://www.ifcopenshell.org/ns}name]')[0]
|
|
||||||
nm = group.attrib["{http://www.ifcopenshell.org/ns}name"]
|
nm = group.attrib["{http://www.ifcopenshell.org/ns}name"]
|
||||||
m4 = np.array(json.loads(group.attrib["{http://www.ifcopenshell.org/ns}plane"]))
|
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"]))
|
m3 = np.array(json.loads(group.attrib["{http://www.ifcopenshell.org/ns}matrix3"]))
|
||||||
@@ -503,7 +506,7 @@ class CreateDrawing(bpy.types.Operator):
|
|||||||
|
|
||||||
for projection in projections:
|
for projection in projections:
|
||||||
boundary_lines = []
|
boundary_lines = []
|
||||||
for path in projection.findall('./{http://www.w3.org/2000/svg}path'):
|
for path in projection.findall("./{http://www.w3.org/2000/svg}path"):
|
||||||
start, end = [co[1:].split(",") for co in path.attrib["d"].split()]
|
start, end = [co[1:].split(",") for co in path.attrib["d"].split()]
|
||||||
boundary_lines.append(shapely.LineString([start, end]))
|
boundary_lines.append(shapely.LineString([start, end]))
|
||||||
unioned_boundaries = shapely.union_all(shapely.GeometryCollection(boundary_lines))
|
unioned_boundaries = shapely.union_all(shapely.GeometryCollection(boundary_lines))
|
||||||
@@ -517,23 +520,39 @@ class CreateDrawing(bpy.types.Operator):
|
|||||||
internal_point = centroid if polygon.contains(centroid) else polygon.representative_point()
|
internal_point = centroid if polygon.contains(centroid) else polygon.representative_point()
|
||||||
if internal_point:
|
if internal_point:
|
||||||
internal_point = [internal_point.x, internal_point.y]
|
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)
|
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")]
|
inside_elements = [e for e in tree.select(self.pythonize(a)) if not e.is_a("IfcAnnotation")]
|
||||||
if not inside_elements:
|
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")]
|
elements = [
|
||||||
|
e
|
||||||
|
for e in tree.select_ray(self.pythonize(a), self.pythonize(b - a))
|
||||||
|
if not e.instance.is_a("IfcAnnotation")
|
||||||
|
]
|
||||||
if elements:
|
if elements:
|
||||||
path = etree.SubElement(group, "path")
|
path = etree.Element("path")
|
||||||
d = "M" + " L".join([",".join([str(o) for o in co]) for co in polygon.exterior.coords[0:-1]]) + " Z"
|
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:
|
for interior in polygon.interiors:
|
||||||
d += " M" + " L".join([",".join([str(o) for o in co]) for co in interior.coords[0:-1]]) + " Z"
|
d += (
|
||||||
|
" M"
|
||||||
|
+ " L".join([",".join([str(o) for o in co]) for co in interior.coords[0:-1]])
|
||||||
|
+ " Z"
|
||||||
|
)
|
||||||
path.attrib["d"] = d
|
path.attrib["d"] = d
|
||||||
classes = self.get_svg_classes(ifc.by_id(elements[0].instance.id()))
|
classes = self.get_svg_classes(ifc.by_id(elements[0].instance.id()))
|
||||||
classes.append("surface")
|
classes.append("surface")
|
||||||
path.set("class", " ".join(list(classes)))
|
path.set("class", " ".join(list(classes)))
|
||||||
|
group.insert(0, path)
|
||||||
results = etree.tostring(root)
|
|
||||||
|
|
||||||
if self.camera.data.BIMCameraProperties.calculate_svgfill_surfaces:
|
if self.camera.data.BIMCameraProperties.calculate_svgfill_surfaces:
|
||||||
|
results = etree.tostring(root).decode("utf8")
|
||||||
svg_data_1 = results
|
svg_data_1 = results
|
||||||
from xml.dom.minidom import parseString
|
from xml.dom.minidom import parseString
|
||||||
|
|
||||||
@@ -624,7 +643,11 @@ class CreateDrawing(bpy.types.Operator):
|
|||||||
if iteration != num_passes:
|
if iteration != num_passes:
|
||||||
semantics[pi] = (inside_elements[0], -1)
|
semantics[pi] = (inside_elements[0], -1)
|
||||||
else:
|
else:
|
||||||
elements = [e for e in tree.select_ray(self.pythonize(a), self.pythonize(b - a)) if not e.instance.is_a("IfcAnnotation")]
|
elements = [
|
||||||
|
e
|
||||||
|
for e in tree.select_ray(self.pythonize(a), self.pythonize(b - a))
|
||||||
|
if not e.instance.is_a("IfcAnnotation")
|
||||||
|
]
|
||||||
|
|
||||||
if elements:
|
if elements:
|
||||||
classes = self.get_svg_classes(ifc.by_id(elements[0].instance.id()))
|
classes = self.get_svg_classes(ifc.by_id(elements[0].instance.id()))
|
||||||
@@ -682,16 +705,56 @@ class CreateDrawing(bpy.types.Operator):
|
|||||||
# This generally shouldn't happen
|
# This generally shouldn't happen
|
||||||
g1.appendChild(g2)
|
g1.appendChild(g2)
|
||||||
|
|
||||||
data = dom1.toxml()
|
results = dom1.toxml()
|
||||||
data = data.encode("ascii", "xmlcharrefreplace")
|
results = results.encode("ascii", "xmlcharrefreplace")
|
||||||
|
|
||||||
results = data
|
|
||||||
with profile("Post processing"):
|
|
||||||
root = etree.fromstring(results)
|
root = etree.fromstring(results)
|
||||||
self.move_projection_to_bottom(root)
|
|
||||||
self.merge_linework_and_add_metadata(root)
|
# Spaces are handled as a special case, since they are often overlayed
|
||||||
with open(svg_path, "wb") as svg:
|
# in addition to elements. For example, a space should not obscure
|
||||||
svg.write(etree.tostring(root))
|
# other elements in projection. Spaces should also not override cut
|
||||||
|
# elements but instead be drawn in addition to cut elements.
|
||||||
|
spaces = tool.Drawing.get_drawing_spaces(self.camera_element)
|
||||||
|
|
||||||
|
group = root.findall(".//{http://www.w3.org/2000/svg}g")[0]
|
||||||
|
|
||||||
|
self.svg_writer.calculate_scale()
|
||||||
|
x_offset = self.svg_writer.raw_width / 2
|
||||||
|
y_offset = self.svg_writer.raw_height / 2
|
||||||
|
|
||||||
|
for space in spaces:
|
||||||
|
obj = tool.Ifc.get_object(space)
|
||||||
|
if not obj or not tool.Drawing.is_intersecting_camera(obj, self.camera):
|
||||||
|
continue
|
||||||
|
verts, edges = tool.Drawing.bisect_mesh(obj, self.camera)
|
||||||
|
verts = [self.svg_writer.project_point_onto_camera(Vector(v)) for v in verts]
|
||||||
|
line_strings = [
|
||||||
|
shapely.LineString(
|
||||||
|
[
|
||||||
|
(
|
||||||
|
(x_offset + verts[e[0]][0]) * self.svg_writer.svg_scale,
|
||||||
|
(y_offset - verts[e[0]][1]) * self.svg_writer.svg_scale,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
(x_offset + verts[e[1]][0]) * self.svg_writer.svg_scale,
|
||||||
|
(y_offset - verts[e[1]][1]) * self.svg_writer.svg_scale,
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
for e in edges
|
||||||
|
]
|
||||||
|
closed_polygons = shapely.polygonize(line_strings)
|
||||||
|
for polygon in closed_polygons.geoms:
|
||||||
|
classes = self.get_svg_classes(space)
|
||||||
|
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"
|
||||||
|
path.attrib["d"] = d
|
||||||
|
path.set("class", " ".join(list(classes)))
|
||||||
|
group.append(path)
|
||||||
|
|
||||||
|
with open(svg_path, "wb") as svg:
|
||||||
|
svg.write(etree.tostring(root))
|
||||||
|
|
||||||
return svg_path
|
return svg_path
|
||||||
|
|
||||||
@@ -748,13 +811,12 @@ class CreateDrawing(bpy.types.Operator):
|
|||||||
value = ifcopenshell.util.selector.get_element_value(element, key)
|
value = ifcopenshell.util.selector.get_element_value(element, key)
|
||||||
if value:
|
if value:
|
||||||
classes.append(
|
classes.append(
|
||||||
tool.Drawing.canonicalise_class_name(key)
|
tool.Drawing.canonicalise_class_name(key) + "-" + tool.Drawing.canonicalise_class_name(str(value))
|
||||||
+ "-"
|
|
||||||
+ tool.Drawing.canonicalise_class_name(str(value))
|
|
||||||
)
|
)
|
||||||
return classes
|
return classes
|
||||||
|
|
||||||
def merge_linework_and_add_metadata(self, root):
|
def merge_linework_and_add_metadata(self, root):
|
||||||
|
group = root.findall(".//{http://www.w3.org/2000/svg}g")[0]
|
||||||
joined_paths = {}
|
joined_paths = {}
|
||||||
|
|
||||||
ifc = tool.Ifc.get()
|
ifc = tool.Ifc.get()
|
||||||
@@ -796,16 +858,15 @@ class CreateDrawing(bpy.types.Operator):
|
|||||||
elif type(merged_polygons) == shapely.Polygon:
|
elif type(merged_polygons) == shapely.Polygon:
|
||||||
merged_polygons = [merged_polygons]
|
merged_polygons = [merged_polygons]
|
||||||
|
|
||||||
root_g = root.findall(".//{http://www.w3.org/2000/svg}g")[0]
|
|
||||||
for polygon in merged_polygons:
|
for polygon in merged_polygons:
|
||||||
g = etree.SubElement(root, "g")
|
g = etree.Element("g")
|
||||||
path = etree.SubElement(g, "path")
|
path = etree.SubElement(g, "path")
|
||||||
d = "M" + " L".join([",".join([str(o) for o in co]) for co in polygon.exterior.coords[0:-1]]) + " Z"
|
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:
|
for interior in polygon.interiors:
|
||||||
d += " M" + " L".join([",".join([str(o) for o in co]) for co in interior.coords[0:-1]]) + " Z"
|
d += " M" + " L".join([",".join([str(o) for o in co]) for co in interior.coords[0:-1]]) + " Z"
|
||||||
path.attrib["d"] = d
|
path.attrib["d"] = d
|
||||||
g.set("class", " ".join(list(classes)))
|
g.set("class", " ".join(list(classes)))
|
||||||
root_g.append(g)
|
group.append(g)
|
||||||
|
|
||||||
def drawing_to_model_co(self, m44, m4, xy, z=0.0):
|
def drawing_to_model_co(self, m44, m4, xy, z=0.0):
|
||||||
xyzw = m44 @ np.array(xy + [z, 1.0])
|
xyzw = m44 @ np.array(xy + [z, 1.0])
|
||||||
@@ -816,14 +877,12 @@ class CreateDrawing(bpy.types.Operator):
|
|||||||
return tuple(map(float, arr))
|
return tuple(map(float, arr))
|
||||||
|
|
||||||
def move_projection_to_bottom(self, root):
|
def move_projection_to_bottom(self, root):
|
||||||
# https://stackoverflow.com/questions/36018627/sorting-child-elements-with-lxml-based-on-attribute-value
|
# 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")
|
group = root.find("{http://www.w3.org/2000/svg}g")
|
||||||
if self.camera.data.BIMCameraProperties.calculate_svgfill_surfaces:
|
projection = group.find("{http://www.w3.org/2000/svg}g[@class='projection']")
|
||||||
# SVGFill already places the projection in the right spot.
|
projection.getparent().remove(projection)
|
||||||
return
|
group.insert(0, projection)
|
||||||
# group[:] = sorted(group, key=lambda e : "projection" in e.get("class"))
|
|
||||||
if group is not None:
|
|
||||||
group[:] = reversed(group)
|
|
||||||
|
|
||||||
def generate_annotation(self, context):
|
def generate_annotation(self, context):
|
||||||
if not self.cprops.has_annotation:
|
if not self.cprops.has_annotation:
|
||||||
|
|||||||
@@ -1342,6 +1342,7 @@ class Drawing(blenderbim.core.tool.Drawing):
|
|||||||
elements = set(ifc_file.by_type("IfcElement") + ifc_file.by_type("IfcSpatialStructureElement"))
|
elements = set(ifc_file.by_type("IfcElement") + ifc_file.by_type("IfcSpatialStructureElement"))
|
||||||
else:
|
else:
|
||||||
elements = set(ifc_file.by_type("IfcElement") + ifc_file.by_type("IfcSpatialElement"))
|
elements = set(ifc_file.by_type("IfcElement") + ifc_file.by_type("IfcSpatialElement"))
|
||||||
|
elements = {e for e in elements if e.is_a() != "IfcSpace"}
|
||||||
annotations = tool.Drawing.get_group_elements(tool.Drawing.get_drawing_group(drawing))
|
annotations = tool.Drawing.get_group_elements(tool.Drawing.get_drawing_group(drawing))
|
||||||
elements.update(annotations)
|
elements.update(annotations)
|
||||||
|
|
||||||
@@ -1351,6 +1352,19 @@ class Drawing(blenderbim.core.tool.Drawing):
|
|||||||
elements -= set(ifc_file.by_type("IfcOpeningElement"))
|
elements -= set(ifc_file.by_type("IfcOpeningElement"))
|
||||||
return elements
|
return elements
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_drawing_spaces(cls, drawing):
|
||||||
|
ifc_file = tool.Ifc.get()
|
||||||
|
pset = ifcopenshell.util.element.get_psets(drawing).get("EPset_Drawing", {})
|
||||||
|
include = pset.get("Include", None)
|
||||||
|
elements = set(ifc_file.by_type("IfcSpace"))
|
||||||
|
if include:
|
||||||
|
elements = set(ifcopenshell.util.selector.Selector.parse(ifc_file, include, elements=elements))
|
||||||
|
exclude = pset.get("Exclude", None)
|
||||||
|
if exclude:
|
||||||
|
elements -= set(ifcopenshell.util.selector.Selector.parse(ifc_file, exclude, elements=elements))
|
||||||
|
return elements
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_annotation_element(cls, element):
|
def get_annotation_element(cls, element):
|
||||||
for rel in element.HasAssignments:
|
for rel in element.HasAssignments:
|
||||||
@@ -1464,3 +1478,69 @@ class Drawing(blenderbim.core.tool.Drawing):
|
|||||||
should_sync_changes_first=True,
|
should_sync_changes_first=True,
|
||||||
)
|
)
|
||||||
break
|
break
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def is_intersecting_camera(cls, obj, camera):
|
||||||
|
# Based on separating axis theorem
|
||||||
|
plane_co = camera.matrix_world.translation
|
||||||
|
plane_no = camera.matrix_world.col[2].xyz
|
||||||
|
|
||||||
|
# Broadphase check using the bounding box
|
||||||
|
bounding_box_world_coords = [obj.matrix_world @ Vector(coord) for coord in obj.bound_box]
|
||||||
|
bounding_box_signed_distances = [plane_no.dot(v - plane_co) for v in bounding_box_world_coords]
|
||||||
|
|
||||||
|
pos_exists_bb = any(d > 0 for d in bounding_box_signed_distances)
|
||||||
|
neg_exists_bb = any(d < 0 for d in bounding_box_signed_distances)
|
||||||
|
|
||||||
|
if not (pos_exists_bb and neg_exists_bb):
|
||||||
|
return False
|
||||||
|
|
||||||
|
bm = bmesh.new()
|
||||||
|
bm.from_mesh(obj.data)
|
||||||
|
|
||||||
|
# Transform the vertices to world space
|
||||||
|
mesh_mat = obj.matrix_world
|
||||||
|
bm.transform(mesh_mat)
|
||||||
|
|
||||||
|
# Calculate the signed distances of all vertices from the plane
|
||||||
|
signed_distances = [plane_no.dot(v.co - plane_co) for v in bm.verts]
|
||||||
|
|
||||||
|
bm.free()
|
||||||
|
|
||||||
|
# Check for intersection
|
||||||
|
pos_exists = any(d > 0 for d in signed_distances)
|
||||||
|
neg_exists = any(d < 0 for d in signed_distances)
|
||||||
|
|
||||||
|
return pos_exists and neg_exists
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def bisect_mesh(cls, obj, camera):
|
||||||
|
camera_matrix = obj.matrix_world.inverted() @ camera.matrix_world
|
||||||
|
plane_co = camera_matrix.translation
|
||||||
|
plane_no = camera_matrix.col[2].xyz
|
||||||
|
|
||||||
|
global_offset = camera.matrix_world.col[2].xyz * -camera.data.clip_start
|
||||||
|
|
||||||
|
bm = bmesh.new()
|
||||||
|
bm.from_mesh(obj.data)
|
||||||
|
|
||||||
|
# Run the bisect operation
|
||||||
|
geom = bm.verts[:] + bm.edges[:] + bm.faces[:]
|
||||||
|
results = bmesh.ops.bisect_plane(bm, geom=geom, dist=0.0001, plane_co=plane_co, plane_no=plane_no)
|
||||||
|
|
||||||
|
vert_map = {}
|
||||||
|
verts = []
|
||||||
|
edges = []
|
||||||
|
i = 0
|
||||||
|
for geom in results["geom_cut"]:
|
||||||
|
if isinstance(geom, bmesh.types.BMVert):
|
||||||
|
verts.append(tuple((obj.matrix_world @ geom.co) + global_offset))
|
||||||
|
vert_map[geom.index] = i
|
||||||
|
i += 1
|
||||||
|
else:
|
||||||
|
# It seems as though edges always appear after verts
|
||||||
|
edges.append([vert_map[v.index] for v in geom.verts])
|
||||||
|
|
||||||
|
bm.free()
|
||||||
|
|
||||||
|
return verts, edges
|
||||||
|
|||||||
Reference in New Issue
Block a user