# Forum

### Author Topic: Export Model Clip  (Read 5708 times)

#### SamT

• Newbie
• Posts: 40
##### Export Model Clip
« on: July 28, 2020, 04:26:40 AM »
Export model with clip to boundary shapes seems to use the convex hull of the shape rather than the shape itself. Is this expected behavior?
« Last Edit: July 28, 2020, 04:39:02 AM by SamT »

#### Alexey Pasumansky

• Agisoft Technical Support
• Hero Member
• Posts: 14847
##### Re: Export Model Clip
« Reply #1 on: August 08, 2020, 04:06:47 PM »
Hello SamT,

Currently it is expected behavior for Export Model procedure.

As a workaround it is possible to delete the polygons which do not any any vertex inside the boundary shape using Python and export the cropped model.
Best regards,
Alexey Pasumansky,
Agisoft LLC

#### SamT

• Newbie
• Posts: 40
##### Re: Export Model Clip
« Reply #2 on: August 10, 2020, 01:21:23 AM »
Thanks Alexey,

Do you have an example script for cropping to a polygon with python? I have some code that does it but it is very slow.

#### Alexey Pasumansky

• Agisoft Technical Support
• Hero Member
• Posts: 14847
##### Re: Export Model Clip
« Reply #3 on: August 10, 2020, 01:07:12 PM »
Hello SamT,

You can try the following script to crop the mesh to the selected shapes:

Code: [Select]
`import Metashape, timefrom PySide2 import QtGui, QtCore, QtWidgetsdef point_inside(point, poly): x, y = point.x, point.y inside = False p1x, p1y = poly[0] for i in range(len(poly) + 1): p2x, p2y = poly[i % len(poly)] if y >= min(p1y, p2y): if y <= max(p1y, p2y): if x <= max(p1x, p2x): if p1y != p2y: xinters = (y - p1y) * (p2x - p1x) / (p2y - p1y) + p1x if p1x == p2x or x <= xinters: inside = not inside p1x, p1y = p2x, p2y return inside########################### class CropMesh(QtWidgets.QDialog): def __init__(self, parent): QtWidgets.QDialog.__init__(self, parent) self.btnQuit = QtWidgets.QPushButton("Close") self.btnP1 = QtWidgets.QPushButton("Start") self.pBar = QtWidgets.QProgressBar() self.pBar.setTextVisible(False) layout = QtWidgets.QGridLayout() layout.addWidget(self.pBar,0, 0, 1, 2) layout.addWidget(self.btnP1, 2, 0) layout.addWidget(self.btnQuit, 2, 1) self.setLayout(layout) proc_height = lambda: self.estimate_height() QtCore.QObject.connect(self.btnP1, QtCore.SIGNAL("clicked()"), proc_height) QtCore.QObject.connect(self.btnQuit, QtCore.SIGNAL("clicked()"), self, QtCore.SLOT("reject()")) self.exec() def estimate_height(self): doc = Metashape.app.document chunk = doc.chunk if not chunk: print("Empty document, script aborted.") return 0 model = chunk.model if not model: print("No mesh model, script aborted.") return 0 if not chunk.shapes: print("No shapes, script aborted.") shapes = [shape for shape in chunk.shapes if shape.selected and (shape.type == Metashape.Shape.Type.Polygon)] if len(shapes) < 1: print("No shapes selected, script aborted.") return 0 #shape = shapes[0] t0 = time.time() print("Script started...") self.pBar.setValue(0) app.processEvents() self.btnP1.setDisabled(True) self.btnQuit.setDisabled(True) T = chunk.transform.matrix m = chunk.crs.localframe(T.mulp(chunk.region.center)) M = m* T scale = chunk.transform.scale m_vertices = model.vertices faces = model.faces s_vertices = dict() polygons = dict() INIT_PROG = 1 self.pBar.setValue(INIT_PROG) app.processEvents() SHAPES_PROG = 10 processed = 0 for shape in shapes: s_vertices[shape] = [m.mulp(chunk.crs.unproject(v)) for v in shape.vertices] # polygons[shape] = [[v.x, v.y] for v in s_vertices[shape]] processed += 1 self.pBar.setValue(INIT_PROG + int(SHAPES_PROG * processed / len(shapes))) app.processEvents() FACE_PROG = 80 face_step = int(len(faces) / FACE_PROG) iface = 0 processed = 0 for face in faces: for shape in shapes: vert_center = list() for i in range(3): vert_center.append(m_vertices[face.vertices[i]].coord) vert_center[i] = M.mulp(vert_center[i]) sum_area = False for i in range(3): if point_inside(vert_center[i], polygons[shape]): sum_area = True if sum_area: v1 = vert_center[0] - vert_center[1] v2 = vert_center[0] - vert_center[2] area = Metashape.Vector.cross(v2, v1).norm() / 2 #* scale face.selected = True iface += 1 if iface > face_step: iface = iface - face_step processed += 1 self.pBar.setValue(INIT_PROG + SHAPES_PROG + int(processed)) app.processEvents() OUTPUT_PROG = 9 processed = 0 chunk.model.cropSelection() self.pBar.setValue(100) app.processEvents() Metashape.app.update() t1 = time.time() t1 -= t0 t1 = float(t1) print("Script finished in " + "{:.2f}".format(t1) + " seconds.") self.btnP1.setDisabled(False) self.btnQuit.setDisabled(False) app.processEvents() return 1 def crop_mesh(): global app app = QtWidgets.QApplication.instance() parent = app.activeWindow() dlg = CropMesh(parent) label = "Custom menu/Crop mesh by shape"Metashape.app.addMenuItem(label, crop_mesh)print("To execute this script press {}".format(label))`
Best regards,
Alexey Pasumansky,
Agisoft LLC

#### SamT

• Newbie
• Posts: 40
##### Re: Export Model Clip
« Reply #4 on: August 11, 2020, 03:13:03 AM »
That is excellent thanks Alexey.

I was re-projecting the vertices to the shape CRS which was very time consuming.

#### Alexey Pasumansky

• Agisoft Technical Support
• Hero Member
• Posts: 14847
##### Re: Export Model Clip
« Reply #5 on: March 05, 2022, 05:18:42 PM »
Updated script for version 1.8.x

Code: [Select]
`import Metashape, timefrom PySide2 import QtGui, QtCore, QtWidgetsdef flatten_list(input_list): if not input_list: return [] output_list = [] for i in input_list: if not isinstance(i, list): output_list.append(i) else: output_list += flatten_list(i) return output_listdef point_inside(point, poly): x, y = point.x, point.y inside = False p1x, p1y = poly[0] for i in range(len(poly) + 1): p2x, p2y = poly[i % len(poly)] if y >= min(p1y, p2y): if y <= max(p1y, p2y): if x <= max(p1x, p2x): if p1y != p2y: xinters = (y - p1y) * (p2x - p1x) / (p2y - p1y) + p1x if p1x == p2x or x <= xinters: inside = not inside p1x, p1y = p2x, p2y return inside########################### class CropMesh(QtWidgets.QDialog): def __init__(self, parent): QtWidgets.QDialog.__init__(self, parent) self.btnQuit = QtWidgets.QPushButton("Close") self.btnP1 = QtWidgets.QPushButton("Start") self.pBar = QtWidgets.QProgressBar() self.pBar.setTextVisible(False) layout = QtWidgets.QGridLayout() layout.addWidget(self.pBar,0, 0, 1, 2) layout.addWidget(self.btnP1, 2, 0) layout.addWidget(self.btnQuit, 2, 1) self.setLayout(layout) proc_height = lambda: self.estimate_height() QtCore.QObject.connect(self.btnP1, QtCore.SIGNAL("clicked()"), proc_height) QtCore.QObject.connect(self.btnQuit, QtCore.SIGNAL("clicked()"), self, QtCore.SLOT("reject()")) self.exec() def estimate_height(self): doc = Metashape.app.document chunk = doc.chunk if not chunk: print("Empty document, script aborted.") return 0 model = chunk.model if not model: print("No mesh model, script aborted.") return 0 if not chunk.shapes: print("No shapes, script aborted.") shapes = [shape for shape in chunk.shapes if shape.selected and (shape.geometry.type == Metashape.Geometry.Type.PolygonType)] if len(shapes) < 1: print("No shapes selected, script aborted.") return 0 #shape = shapes[0] t0 = time.time() print("Script started...") self.pBar.setValue(0) app.processEvents() self.btnP1.setDisabled(True) self.btnQuit.setDisabled(True) T = chunk.transform.matrix m = chunk.crs.localframe(T.mulp(chunk.region.center)) M = m* T scale = chunk.transform.scale m_vertices = model.vertices faces = model.faces s_vertices = dict() polygons = dict() INIT_PROG = 1 self.pBar.setValue(INIT_PROG) app.processEvents() SHAPES_PROG = 10 processed = 0 for shape in shapes: vertices = flatten_list(shape.geometry.coordinates) s_vertices[shape] = [m.mulp(chunk.crs.unproject(v)) for v in vertices] # polygons[shape] = [[v.x, v.y] for v in s_vertices[shape]] processed += 1 self.pBar.setValue(INIT_PROG + int(SHAPES_PROG * processed / len(shapes))) app.processEvents() FACE_PROG = 80 face_step = int(len(faces) / FACE_PROG) iface = 0 processed = 0 for face in faces: for shape in shapes: vert_center = list() for i in range(3): vert_center.append(m_vertices[face.vertices[i]].coord) vert_center[i] = M.mulp(vert_center[i]) sum_area = False for i in range(3): if point_inside(vert_center[i], polygons[shape]): sum_area = True if sum_area: v1 = vert_center[0] - vert_center[1] v2 = vert_center[0] - vert_center[2] area = Metashape.Vector.cross(v2, v1).norm() / 2 #* scale face.selected = True iface += 1 if iface > face_step: iface = iface - face_step processed += 1 self.pBar.setValue(INIT_PROG + SHAPES_PROG + int(processed)) app.processEvents() OUTPUT_PROG = 9 processed = 0 chunk.model.cropSelection() self.pBar.setValue(100) app.processEvents() Metashape.app.update() t1 = time.time() t1 -= t0 t1 = float(t1) print("Script finished in " + "{:.2f}".format(t1) + " seconds.") self.btnP1.setDisabled(False) self.btnQuit.setDisabled(False) app.processEvents() return 1 def crop_mesh(): global app app = QtWidgets.QApplication.instance() parent = app.activeWindow() dlg = CropMesh(parent) label = "Custom menu/Crop mesh by shape"Metashape.app.addMenuItem(label, crop_mesh)print("To execute this script press {}".format(label))`
Best regards,
Alexey Pasumansky,
Agisoft LLC

#### dpitman

• Full Member
• Posts: 246
##### Re: Export Model Clip
« Reply #6 on: March 05, 2022, 06:35:15 PM »
Hi Alexey,

Do all legacy scripts that work with 1.7.X need amended to work with 1.8.X  ?

EDIT: Disregard. I see that the scripts in the GIT repository have already been amended.
« Last Edit: March 05, 2022, 07:14:56 PM by dpitman »

#### agicraft

• Newbie
• Posts: 6
##### Re: Export Model Clip
« Reply #7 on: October 28, 2022, 01:48:43 PM »
Would it be possible to get the script updated to work with the 2.0 pre-release?