class ObjectItem(Item): """The class to visualize (detected) object. It currently supports only rotation around z-axis. """ def __init__(self, scene, object_id, object_type, x, y, yaw, sel_cb=None, selected=False): self.object_id = object_id self.selected = selected self.sel_cb = sel_cb # TODO check bbox type and use rectangle (used now) / ellipse, consider # other angles self.object_type = object_type self.inflate = 1.2 self.hover_ratio = 1.1 self.def_color = QtCore.Qt.gray self.desc = None super(ObjectItem, self).__init__(scene, x, y) self.desc = DescItem(scene, 0, 0, self) self.desc.setFlag(QtGui.QGraphicsItem.ItemIgnoresTransformations) self.update_text() self.setRotation(yaw) if selected: self.set_selected() self._update_desc_pos() self.setZValue(50) def set_color(self, color=QtCore.Qt.gray): self.def_color = color self.update() def _update_desc_pos(self): if self.desc is not None: # make upper left corner of description aligned with left extent of # the (possibly rotated) object bounding box (highlight area) self.desc.setPos( self.mapFromScene( self.x() - self.sceneBoundingRect().width() / 2, self.y() + self.sceneBoundingRect().height() / 2 + self.m2pix(0.01))) def set_pos(self, x, y, parent_coords=False, yaw=None): super(ObjectItem, self).set_pos(x, y, parent_coords, yaw) self._update_desc_pos() self.update_text() def update_text(self): if self.desc is None: return desc = "" desc += translate("ObjectItem", "ID: ") + self.object_id if self.hover: desc += "\n" + translate("ObjectItem", "TYPE: ") + self.object_type.name desc += "\n" + self.get_pos_str() self.desc.set_content(desc) def hover_changed(self): self.update_text() self.update() def boundingRect(self): if not self.scene(): return QtCore.QRectF() lx = self.hover_ratio * self.inflate * \ self.m2pix(self.object_type.bbox.dimensions[0]) ly = self.hover_ratio * self.inflate * \ self.m2pix(self.object_type.bbox.dimensions[1]) p = 1.0 return QtCore.QRectF(-lx / 2 - p, -ly / 2 - p, lx + 2 * p, ly + 2 * p) def paint(self, painter, option, widget): if not self.scene(): return painter.setClipRect(option.exposedRect) painter.setRenderHint(QtGui.QPainter.Antialiasing) lx = self.inflate * self.m2pix(self.object_type.bbox.dimensions[0]) ly = self.inflate * self.m2pix(self.object_type.bbox.dimensions[1]) rr = 10 if self.selected: painter.setBrush(QtCore.Qt.green) painter.setPen(QtCore.Qt.green) painter.drawRoundedRect(-lx / 2 * self.hover_ratio, -ly / 2 * self.hover_ratio, lx * self.hover_ratio, ly * self.hover_ratio, rr, rr, QtCore.Qt.RelativeSize) elif self.hover: painter.setBrush(QtCore.Qt.gray) painter.setPen(QtCore.Qt.gray) painter.drawRoundedRect(-lx / 2 * self.hover_ratio, -ly / 2 * self.hover_ratio, lx * self.hover_ratio, ly * self.hover_ratio, rr, rr, QtCore.Qt.RelativeSize) painter.setBrush(self.def_color) painter.setPen(self.def_color) painter.drawRoundedRect(-lx / 2, -ly / 2, lx, ly, rr, rr, QtCore.Qt.RelativeSize) fr = 1.0 - (self.hover_ratio - 1.0) # fill ratio painter.setBrush(QtCore.Qt.black) painter.setPen(QtCore.Qt.black) painter.drawRoundedRect(-lx / 2 * fr, -ly / 2 * fr, lx * fr, ly * fr, rr, rr, QtCore.Qt.RelativeSize) def cursor_press(self): # TODO call base class method if self.sel_cb is not None: # callback should handle object selection self.sel_cb(self.object_id, self.selected) else: # no callback - object will handle its selection if not self.selected: self.set_selected() else: self.set_selected(False) def set_selected(self, selected=True): if selected: self.selected = True # rospy.logdebug('Object ID ' + self.object_id + ' selected') else: self.selected = False # rospy.logdebug('Object ID ' + self.object_id + ' unselected') self.update()
class ObjectItem(Item): """The class to visualize (detected) object. It currently supports only rotation around z-axis. """ def __init__(self, scene, object_id, object_type, x, y, z, quaternion=(0, 0, 0, 1), sel_cb=None, selected=False, parent=None, horizontal=False): self.object_id = object_id self.selected = selected self.sel_cb = sel_cb self.horizontal = horizontal # TODO check bbox type and use rectangle (used now) / ellipse, consider # other angles self.object_type = object_type self.inflate = 0.01 self.def_color = QtCore.Qt.gray self.lx = 0 self.ly = 0 self.desc = None self.quaternion = (0, 0, 0, 1) self.on_table = False super(ObjectItem, self).__init__(scene, x, y, z, parent=parent) self.desc = DescItem(scene, 0, 0, parent=self) self.desc.setFlag(QtGui.QGraphicsItem.ItemIgnoresTransformations) self.update_text() self.set_orientation(quaternion) if selected: self.set_selected() self._update_desc_pos() self.setZValue(50) def set_color(self, color=QtCore.Qt.gray): self.def_color = color self.update() def _update_desc_pos(self): if self.desc is not None: # make upper left corner of description aligned with left extent of # the (possibly rotated) object bounding box (highlight area) self.desc.setPos( self.mapFromScene( self.x() - self.sceneBoundingRect().width() / 2, self.y() + self.sceneBoundingRect().height() / 2 + self.m2pix(0.01))) def set_pos(self, x, y, z=None, parent_coords=False): super(ObjectItem, self).set_pos(x, y, z, parent_coords) self._update_desc_pos() self.update_text() def get_yaw_axis(self): ax = ((1, 0, 0), (0, 1, 0), (0, 0, 1)) for idx in range(0, len(ax)): res = conversions.qv_mult(self.quaternion, ax[idx]) # TODO euclid dist if conversions.is_close(res[0], 0, abs_tol=0.1) and conversions.is_close(res[1], 0, abs_tol=0.1): return idx return -1 def set_orientation(self, q): self.quaternion = q ax = self.get_yaw_axis() if ax == ObjectItem.Z: self.lx = self.m2pix(self.inflate + self.object_type.bbox.dimensions[0]) self.ly = self.m2pix(self.inflate + self.object_type.bbox.dimensions[1]) sres = conversions.qv_mult(self.quaternion, (1, 0, 0)) angle = math.atan2(sres[1], sres[0]) self.on_table = self.position[2] < self.object_type.bbox.dimensions[2] + 0.05 elif ax in [ObjectItem.X, ObjectItem.Y]: res = conversions.qv_mult(self.quaternion, (0, 0, 1)) self.lx = self.m2pix(self.inflate + self.object_type.bbox.dimensions[2]) # TODO use correct dimension (x/y) - now let's assume that x and y dimensions are same self.ly = self.m2pix(self.inflate + self.object_type.bbox.dimensions[1]) angle = math.atan2(res[1], res[0]) self.on_table = self.position[2] < self.object_type.bbox.dimensions[0] + 0.05 else: self.set_enabled(False, True) return self.setRotation(-angle / (math.pi * 2) * 360) # TODO if not on table - display somewhere list of detected objects or what? self.set_enabled(self.on_table, True) self.update() def update_text(self): if self.desc is None: return desc = "" desc += translate("ObjectItem", "ID: ") + self.object_id if self.hover: desc += "\n" + translate("ObjectItem", "TYPE: ") + self.object_type.name desc += "\n" + self.get_pos_str() self.desc.set_content(desc) def hover_changed(self): self.update_text() self.update() def boundingRect(self): if not self.scene(): return QtCore.QRectF() self.lx = self.hover_ratio * self.inflate * \ self.m2pix(self.object_type.bbox.dimensions[0]) if not self.horizontal: self.ly = self.hover_ratio * self.inflate * \ self.m2pix(self.object_type.bbox.dimensions[1]) else: self.ly = self.hover_ratio * self.inflate * \ self.m2pix(self.object_type.bbox.dimensions[2]) p = 10.0 return QtCore.QRectF(-self.lx / 2 - p, -self.ly / 2 - p, self.lx + 2 * p, self.ly + 2 * p) def paint(self, painter, option, widget): if not self.scene(): return if not self.on_table: return painter.setClipRect(option.exposedRect) painter.setRenderHint(QtGui.QPainter.Antialiasing) # if not self.horizontal: # self.ly = self.m2pix(self.inflate + self.object_type.bbox.dimensions[1]) # else: # self.ly = self.m2pix(self.inflate + self.object_type.bbox.dimensions[2]) rr = 10 painter.setBrush(QtCore.Qt.NoBrush) pen = QtGui.QPen(self.def_color, 5, QtCore.Qt.SolidLine, QtCore.Qt.RoundCap) if self.selected: pen.setColor(QtCore.Qt.green) pen.setWidth(10) elif self.hover: pen.setWidth(10) painter.setPen(pen) painter.drawRoundedRect(-self.lx / 2, -self.ly / 2, self.lx, self.ly, rr, rr, QtCore.Qt.RelativeSize) def cursor_press(self): # TODO call base class method if self.sel_cb is not None: # callback should handle object selection self.sel_cb(self.object_id, self.selected) else: # no callback - object will handle its selection if not self.selected: self.set_selected() else: self.set_selected(False) def set_selected(self, selected=True): if selected: self.selected = True # rospy.logdebug('Object ID ' + self.object_id + ' selected') else: self.selected = False # rospy.logdebug('Object ID ' + self.object_id + ' unselected') self.update()
class SquareItem(Item): # Class constructor def __init__( self, scene, caption, min_x, min_y, square_width, square_height, object_type, poses, grid_points, scene_items, square_changed=None, fixed=False): self.scn = scene self.caption = caption self.object_type = object_type self.scene_items = scene_items self.square_changed = square_changed self.space = 0.05 # is added to bbox of an object self.object_side_length_x = self.object_type.bbox.dimensions[0] + self.space self.object_side_length_y = self.object_type.bbox.dimensions[1] + self.space self.poses = poses self.square = QtGui.QPolygon() self.previous_width = 0 self.previous_height = 0 self.items = [] self.last_corner = "BR" if len(grid_points) == 0: self.min = [min_x, min_y] self.max = [min_x + square_width, min_y + square_height] self.pom_min = self.min # to save original value because y is changed in update_bound self.pom_max = self.max else: self.min = list(min(grid_points)) self.max = list(max(grid_points)) self.orig_x = [] self.orig_y = [] self.desc = None self.dialog = None self.horizontal = False super(SquareItem, self).__init__(scene, min_x, min_y) self.fixed = fixed self.desc = DescItem(scene, self.min[0] - 0.01, self.min[1] - 0.015, self) self.desc.setFlag(QtGui.QGraphicsItem.ItemIgnoresTransformations) self.update_text() self.plus_btn = ButtonItem(scene, self.min[0] - 0.01, self.min[1] - 0.04, "+", self, self.plus_clicked, 1.25, QtCore.Qt.darkCyan, width=0.04) self.minus_btn = ButtonItem(scene, self.min[0] + 0.035, self.min[1] - 0.04, "-", self, self.minus_clicked, 1.25, QtCore.Qt.darkCyan, width=0.04) self.plus_btn.setEnabled(False) self.minus_btn.setEnabled(False) self.pts = [] self.pts.append(SquarePointItem(scene, self.min[0], self.min[1], self, "BL", self.fixed)) # bottom-left corner self.pts.append(SquarePointItem(scene, self.max[0], self.min[1], self, "BR", self.fixed)) # bottom-right corner self.pts.append(SquarePointItem(scene, self.max[0], self.max[1], self, "TR", self.fixed)) # top-right corner self.pts.append(SquarePointItem(scene, self.min[0], self.max[1], self, "TL", self.fixed)) # top-left corner if len(poses) > 0 and self.fixed: # depicts fixed objects for pose in poses: it = PlaceItem( self.scn, "Object", pose.pose.position.x, pose.pose.position.y, pose.pose.position.z, conversions.q2a(pose.pose.orientation), self.object_type, None, place_pose_changed=None, fixed=True, txt=False, parent=self, horizontal=self.horizontal ) self.items.append(it) else: # depicts editable objects for i, pose in enumerate(poses): rot_point = None # to save xy coordinates for rotation point (for rotating objects in a grid) if i == 0: rot = True pom = self.find_corner("TL").get_pos() rot_point = [pom[0] - 0.025, pom[1] + 0.025] else: rot = False it = PlaceItem( self.scn, "Object", pose.pose.position.x, pose.pose.position.y, pose.pose.position.z, conversions.q2a(pose.pose.orientation), self.object_type, None, place_pose_changed=None, fixed=False, txt=False, rot=rot, rot_point=rot_point, rotation_changed=self.items_rotation_changed, parent=self, horizontal=self.horizontal ) it.update_point() self.items.append(it) if self.items: # rotation of all objects in a grid (list of all objects is handed over to # the first object. When the first object is rotated, all objects are # rotated) self.items[0].set_other_items(self.items[1:]) self.plus_btn.setEnabled(True) self.minus_btn.setEnabled(True) self.dialog = DialogItem(self.scene(), self.pix2m(self.scene().width() / 2), 0.1, translate( "Place item", "Object place pose options"), [ translate( "Place item", "Rotate |"), translate( "Place item", "Rotate --") ], self.dialog_cb) if self.square_changed is not None: # to save grid points and objects into the ProgramItem message self.square_changed(self.get_square_points(), self.items) self.update_bound() self.update() ''' Method updates text below the grid. ''' def update_text(self): if self.desc is None: return desc = self.caption self.desc.set_content(desc) ''' Method is called when "+" button is pushed. It increase spacing between objects and walls of the box, and between objects. ''' def plus_clicked(self, btn): self.space += 0.01 self.point_changed(True, self.last_corner) ''' Method is called when "-" button is pushed. It decrease spacing between objects and walls of the box, and between objects. ''' def minus_clicked(self, btn): if self.space > 0.02: self.space -= 0.01 self.point_changed(True, self.last_corner) ''' Method updates the bounding rectangle. ''' def update_bound(self): self.min[0] = self.pix2m(self.pts[0].x()) self.min[1] = self.pix2m(self.pts[0].y()) self.max[0] = self.pix2m(self.pts[0].x()) self.max[1] = self.pix2m(self.pts[0].y()) self.orig_x = [] self.orig_y = [] for pt in self.pts: p = (self.pix2m(pt.x()), self.pix2m(pt.y())) if p[0] < self.min[0]: self.min[0] = p[0] if p[1] < self.min[1]: self.min[1] = p[1] if p[0] > self.max[0]: self.max[0] = p[0] if p[1] > self.max[1]: self.max[1] = p[1] point = pt.get_pos() self.orig_x.append(point[0]) self.orig_y.append(point[1]) self.pom_min = [min(self.orig_x), min(self.orig_y)] self.pom_max = [max(self.orig_x), max(self.orig_y)] ''' Method returns a required corner. ''' def find_corner(self, corner): for pt in self.pts: if pt.get_corner() == corner: return pt return None ''' Metoda pre vykreslovanie gridu a objektov v nom. Je volana vzdy, ked sa pohne s niektorym rohom. Rovnomerne rozmiestnuje objekty v gride, kontroluje ci nie su v kolizii. Zaistuje ukladanie poloh bodov a gridu do spravy ProgramItem. Method depics the gird and objects in it. It is called, when some corner is moved. It secures even distribution of objects in the grid, checks collisions. Also secures saving grid points and positions of objects into the ProgramItem message. ''' def point_changed(self, finished=False, corner=""): if self.fixed: return self.prepareGeometryChange() corner = corner # update of bounding rect self.update_bound() for pt in self.pts: if (pt.get_corner() == "BR") and pt.get_changed(): self.find_corner("TR").setPos(pt.x(), self.find_corner("TR").y()) self.find_corner("BL").setPos(self.find_corner("BL").x(), pt.y()) self.desc.setPos(self.m2pix(self.min[0] - 0.01), self.m2pix(self.max[1] + 0.015)) corner = "BR" pt.set_changed(False) elif (pt.get_corner() == "BL") and pt.get_changed(): self.find_corner("TL").setPos(pt.x(), self.find_corner("TL").y()) self.find_corner("BR").setPos(self.find_corner("BR").x(), pt.y()) self.desc.setPos(self.m2pix(self.min[0] - 0.01), self.m2pix(self.max[1] + 0.015)) corner = "BL" pt.set_changed(False) elif (pt.get_corner() == "TL") and pt.get_changed(): self.find_corner("BL").setPos(pt.x(), self.find_corner("BL").y()) self.find_corner("TR").setPos(self.find_corner("TR").x(), pt.y()) self.desc.setPos(self.m2pix(self.min[0] - 0.01), self.m2pix(self.max[1] + 0.015)) corner = "TL" pt.set_changed(False) elif (pt.get_corner() == "TR") and pt.get_changed(): self.find_corner("BR").setPos(pt.x(), self.find_corner("BR").y()) self.find_corner("TL").setPos(self.find_corner("TL").x(), pt.y()) self.desc.setPos(self.m2pix(self.min[0] - 0.01), self.m2pix(self.max[1] + 0.015)) corner = "TR" pt.set_changed(False) self.plus_btn.setPos(self.m2pix(self.min[0] - 0.01), self.m2pix(self.max[1] + 0.04)) self.minus_btn.setPos(self.m2pix(self.min[0] + 0.035), self.m2pix(self.max[1] + 0.04)) if corner != "": self.object_side_length_x = self.object_type.bbox.dimensions[0] + self.space if not self.horizontal: self.object_side_length_y = self.object_type.bbox.dimensions[1] + self.space else: self.object_side_length_y = self.object_type.bbox.dimensions[2] + self.space width_count = int( modf( round( (((self.max[0] - self.min[0]) - self.space) / self.object_side_length_x), 5))[1]) height_count = int( modf( round( (((self.max[1] - self.min[1]) - self.space) / self.object_side_length_y), 5))[1]) if self.previous_width != width_count or self.previous_height != height_count: ps = PoseStamped() if corner == "BR" or corner == "TR": ps.pose.position.x = self.pom_min[0] + self.space / 2 + self.object_side_length_x / 2 else: ps.pose.position.x = self.pom_max[0] - self.space / 2 - self.object_side_length_x / 2 if corner == "BR" or corner == "BL": ps.pose.position.y = self.pom_max[1] - self.space / 2 - self.object_side_length_y / 2 else: ps.pose.position.y = self.pom_min[1] + self.space / 2 + self.object_side_length_y / 2 ps.pose.orientation.w = 1.0 if self.items: rotation = self.items[0].rotation() else: rotation = 0.0 for it in self.items: self.scn.removeItem(it) del self.items[:] for i in range(0, height_count): for j in range(0, width_count): rot_point = None # to save xy coordinates for rotation point (for rotating objects in a grid) if corner == "BR" and i == 0 and j == 0: rot = True pom = self.find_corner("TL").get_pos() rot_point = [pom[0] - 0.025, pom[1] + 0.025] elif corner == "BL" and i == 0 and j == 0: rot = True pom = self.find_corner("TR").get_pos() rot_point = [pom[0] + 0.025, pom[1] + 0.025] elif corner == "TR" and i == 0 and j == 0: rot = True pom = self.find_corner("BL").get_pos() rot_point = [pom[0] - 0.025, pom[1] - 0.025] elif corner == "TL" and i == 0 and j == 0: rot = True pom = self.find_corner("BR").get_pos() rot_point = [pom[0] + 0.025, pom[1] - 0.025] else: rot = False it = PlaceItem( self.scn, "Object", ps.pose.position.x, ps.pose.position.y, ps.pose.position.z, conversions.q2a(ps.pose.orientation), self.object_type, None, place_pose_changed=None, fixed=False, txt=False, rot=rot, rot_point=rot_point, rotation_changed=self.items_rotation_changed, parent=self, horizontal=self.horizontal ) it.setRotation(rotation) it.update_point() self.items.append(it) if corner == "BR" or corner == "TR": ps.pose.position.x += self.object_side_length_x # BR TR else: ps.pose.position.x -= self.object_side_length_x # TL BL if corner == "BR" or corner == "TR": ps.pose.position.x = self.pom_min[0] + self.space / 2 + self.object_side_length_x / 2 # BR a TR else: ps.pose.position.x = self.pom_max[0] - self.space / 2 - self.object_side_length_x / 2 # TL BL if corner == "BR" or corner == "BL": ps.pose.position.y -= self.object_side_length_y + self.space / 2 # BR BL else: ps.pose.position.y += self.object_side_length_y + self.space / 2 # TL TR self.previous_width = width_count self.previous_height = height_count self.last_corner = corner # rotation of all objects in a grid (list of all objects is handed over to # the first object. When the first object is rotated, all objects are # rotated) if self.items: self.items[0].set_other_items(self.items[1:]) if finished and self.square_changed is not None: # even distribution of objects in the grid if self.items: new_object_length_x = ((self.pom_max[0] - self.pom_min[0]) - self.space) / self.previous_width new_object_length_y = ((self.pom_max[1] - self.pom_min[1]) - self.space) / self.previous_height for i, it in enumerate(self.items): if self.last_corner == "BR" or self.last_corner == "TR": new_x = self.pom_min[0] + self.space / 2 + new_object_length_x / \ 2 + new_object_length_x * (i % self.previous_width) else: new_x = self.pom_max[0] - self.space / 2 - new_object_length_x / \ 2 - new_object_length_x * (i % self.previous_width) if self.last_corner == "BR" or self.last_corner == "BL": new_y = self.pom_max[1] - self.space / 2 - new_object_length_y / 2 - new_object_length_y * ( i / self.previous_width) else: new_y = self.pom_min[1] + self.space / 2 + new_object_length_y / 2 + new_object_length_y * ( i / self.previous_width) it.set_pos(new_x, new_y) it.update_point() for it in self.items: # to check if there are still some collisions it.item_moved() self.plus_btn.setEnabled(True) self.minus_btn.setEnabled(True) if self.last_corner == "BR" or self.last_corner == "BL": # TODO: skontrolovat ci nema byt TR a TL!! self.items.reverse() # we want robot always to place object from furthest line in_collision = False for it in self.items: # to check collisions if it.in_collision: in_collision = True break if in_collision: # to save only grid points into the ProgramItem message self.square_changed(self.get_square_points(), []) else: # to save grid points and objects into the ProgramItem message self.square_changed(self.get_square_points(), self.items) self.update() def dialog_cb(self, idx): if idx == 0: self.horizontal = False self.point_changed(True, "BR") else: self.horizontal = True self.point_changed(True, "BR") ''' Method returns grid points. ''' def get_square_points(self): pts = [] for pt in self.pts: pts.append(pt.get_pos()) return pts ''' Method which is called after releasing the grid. It saves new positions of grid points and objects into the ProgramItem message. It checks collisions between grid objects and scene objects. ''' def cursor_release(self): if self.fixed: return for it in self.items: it.item_moved() if self.square_changed is not None: in_collision = False for it in self.items: # to check collisions if it.in_collision: in_collision = True break if in_collision: # to save only grid points into the ProgramItem message self.square_changed(self.get_square_points(), []) else: # to save grid points and objects into the ProgramItem message self.square_changed(self.get_square_points(), self.items) def cursor_press(self): pass ''' Method saves new rotations after rotating objects. ''' def items_rotation_changed(self, items): # to save grid points and objects into the ProgramItem message self.square_changed(self.get_square_points(), items) ''' Method returns the shape of this grid as a QPainterPath in local coordinates. ''' def shape(self): path = QtGui.QPainterPath() path.addPolygon(QtGui.QPolygonF(self.square)) return path ''' Method defines the outer bounds of the item as a rectangle. ''' def boundingRect(self): return QtCore.QRectF( self.m2pix( self.min[0]) - 2.5, self.m2pix( self.min[1]) - 2.5, self.m2pix( self.max[0] - self.min[0]) + 5, self.m2pix( self.max[1] - self.min[1]) + 5) ''' Method paints the grid. ''' def paint(self, painter, option, widget): painter.setClipRect(option.exposedRect) painter.setRenderHint(QtGui.QPainter.Antialiasing) pen = QtGui.QPen() pen.setStyle(QtCore.Qt.DotLine) pen.setWidth(5) pen.setBrush(QtCore.Qt.white) pen.setCapStyle(QtCore.Qt.RoundCap) pen.setJoinStyle(QtCore.Qt.RoundJoin) painter.setPen(pen) self.square = QtGui.QPolygon() for i in range(0, len(self.pts)): self.square.append(self.pts[i].pos().toPoint()) painter.drawPolygon(self.square)
class PolygonItem(Item): def __init__(self, scene, caption, obj_coords=[], poly_points=[], polygon_changed=None, fixed=False): self.caption = caption self.polygon_changed = polygon_changed self.poly = QtGui.QPolygon() self.desc = None super(PolygonItem, self).__init__(scene, 0.5, 0.5) # TODO what should be here? self.fixed = fixed self.convex = True if not self.fixed: self.setFlag(QtGui.QGraphicsItem.ItemIsMovable, True) self.setFlag(QtGui.QGraphicsItem.ItemIsSelectable, True) self.pts = [] if len(obj_coords) > 0: min = [obj_coords[0][0], obj_coords[0][1]] max = [obj_coords[0][0], obj_coords[0][1]] for pt in obj_coords: pt = [pt[0], pt[1]] if pt[0] < min[0]: min[0] = pt[0] if pt[1] < min[1]: min[1] = pt[1] if pt[0] > max[0]: max[0] = pt[0] if pt[1] > max[1]: max[1] = pt[1] pad = 0.02 min[0] -= pad min[1] -= pad max[0] += pad max[1] += pad self.pts.append( PointItem(scene, min[0], min[1], self, self.point_changed)) self.pts.append( PointItem(scene, max[0], min[1], self, self.point_changed)) self.pts.append( PointItem(scene, max[0], max[1], self, self.point_changed)) self.pts.append( PointItem(scene, min[0], max[1], self, self.point_changed)) if self.polygon_changed is not None: self.polygon_changed(self.get_poly_points()) elif len(poly_points) > 0: for pt in poly_points: self.pts.append( PointItem(scene, pt[0], pt[1], self, self.point_changed, fixed)) else: pass # TODO chyba for pt in self.pts: self.poly.append(pt.pos().toPoint()) self.desc = DescItem(scene, 0, 0, self) self.desc.setFlag(QtGui.QGraphicsItem.ItemIgnoresTransformations) self.__update_desc_pos() self.__update_text() self.update() def __update_desc_pos(self): if self.desc is not None: p = self.poly.boundingRect().bottomLeft() + QtCore.QPointF( 0, self.m2pix(0.02)) self.desc.setPos(p) def __update_text(self): if self.desc is None: return desc = self.caption # TODO number of objects in polygon? self.desc.set_content(desc) def point_changed(self, pt, finished): self.poly.setPoint(self.pts.index(pt), pt.pos().toPoint()) ps = self.poly.size() # TODO what to do with shuffled points? should be fixed... for i in range(2, ps + 2): line1 = QtCore.QLineF(self.poly.point(i - 2), self.poly.point((i - 1) % ps)) line2 = QtCore.QLineF(self.poly.point((i - 1) % ps), self.poly.point(i % ps)) a1 = line1.angle() a2 = line2.angle() d = a2 - a1 if d < 0: d = 360 + d if d < 315 and d > 180: self.convex = False break else: self.convex = True self.__update_desc_pos() self.update() if finished and self.convex and self.polygon_changed is not None: self.polygon_changed(self.get_poly_points()) def mouseReleaseEvent(self, event): self.cursor_release() super(Item, self).mouseReleaseEvent(event) def cursor_release(self): # TODO call base class method if self.convex and self.polygon_changed is not None: self.polygon_changed(self.get_poly_points()) def get_poly_points(self): pts = [] try: for pt in self.pts: pts.append(pt.get_pos()) except AttributeError: return None return pts def shape(self): path = QtGui.QPainterPath() path.addPolygon(QtGui.QPolygonF(self.poly)) return path def boundingRect(self): return QtCore.QRectF(self.poly.boundingRect()) def paint(self, painter, option, widget): # TODO detekovat ze je polygon "divny" (prekrouceny) a zcervenat # TODO vypsat kolik obsahuje objektu if not self.scene(): return painter.setClipRect(option.exposedRect) painter.setRenderHint(QtGui.QPainter.Antialiasing) # painter.setPen(QtCore.Qt.white) # painter.drawRect(self.boundingRect()) pen = QtGui.QPen() pen.setStyle(QtCore.Qt.DotLine) pen.setWidth(5) if self.convex: pen.setBrush(QtCore.Qt.white) else: pen.setBrush(QtCore.Qt.red) pen.setCapStyle(QtCore.Qt.RoundCap) pen.setJoinStyle(QtCore.Qt.RoundJoin) painter.setPen(pen) painter.drawPolygon(self.poly)