GUI/ActionsStates.py
class ActionStates(object):
	def __init__(self):
		self.left_button_hold = False
		self.right_button_hold = False
		self.middle_button_hold = False
		self.allow_move = True
		self.add_sketch = False
		self.add_part = False
Data/Drawings.py
class Drawing(Paper, Parameters):
	def __init__(self, document, size=[1, 1], name="New Drawing", header=None, orientation=Paper.Landscape):
		Paper.__init__(self, size, orientation)
		Parameters.__init__(self, name, document.get_parameters())
		self._doc = document
		self._views = []
		self._border_sketch = Sketch(self)
		self._header_sketch = header
		self._margins = [0.02, 0.02, 0.02, 0.02]
		self._fields = {}
		self._annotations = []
		self.generate_border()
		Paper.add_change_handler(self, self.on_paper_changed)

	def on_paper_changed(self, event):
		if event.object is not None:
			if hasattr(event.object, '__iter__'):
				if "name" in event.object:
					if event.object['name'] == "margins" or event.object['name'] == "size":
						print("paper changed")
						self.generate_border()

	@property
	def document(self):
		return self._doc

	@property
	def header_sketch(self):
		return self._header_sketch

	@property
	def header(self):
		return self._header_sketch.name

	@property
	def border_sketch(self):
		return self._border_sketch

	def generate_border(self):
		alpha = "ABCDEFGHIJKLMNOP"
		sketch = self._border_sketch
		sketch.clear()
		self._doc.styles.get_edge_style_by_name("border").thickness = 0.0005
		m = self._margins
		sz = self.size
		border_width = sz[0] - m[0] - m[2]
		max_len = 0.1
		divisions = round(border_width / max_len)
		length = border_width / divisions

		pnt1 = sketch.create_keypoint(m[0], m[3], 0)
		pnt2 = sketch.create_keypoint(sz[0] - m[2], m[3], 0)
		pnt3 = sketch.create_keypoint(sz[0] - m[2], sz[1] - m[1], 0)
		pnt4 = sketch.create_keypoint(m[0], sz[1] - m[1], 0)

		sketch.create_line_edge(pnt1, pnt2).style_name = "border"
		sketch.create_line_edge(pnt2, pnt3).style_name = "border"
		sketch.create_line_edge(pnt3, pnt4).style_name = "border"
		sketch.create_line_edge(pnt4, pnt1).style_name = "border"

		for i in range(0, divisions):
			tkp = sketch.create_keypoint(m[0] + (i + 0.5) * length , 2 * m[3] / 3, 0)
			sketch.create_text(tkp, alpha[i], 0.005)
			tkp = sketch.create_keypoint(m[0] + (i + 0.5) * length,sz[1] - 2 * m[1] / 3, 0)
			sketch.create_text(tkp, alpha[i], 0.005)
			if i > 0:
				pnt1 = sketch.create_keypoint(m[0]  + i * length ,  m[3], 0)
				pnt2 = sketch.create_keypoint(m[0] + i * length , m[3] / 2, 0)
				sketch.create_line_edge(pnt1, pnt2).style_name = "border"
				pnt1 = sketch.create_keypoint(m[0] + i * length , sz[1] - m[1], 0)
				pnt2 = sketch.create_keypoint(m[0] + i * length , sz[1] - m[1] / 2, 0)
				sketch.create_line_edge(pnt1, pnt2).style_name = "border"

		border_height = sz[1] - m[1] - m[3]
		divisions = round(border_height / max_len)
		length = border_height / divisions
		for i in range(0, divisions):
			tkp = sketch.create_keypoint(2 * m[0] / 3, m[3] + (i + 0.5) * length, 0)
			sketch.create_text(tkp, str(i+1), 0.005)
			tkp = sketch.create_keypoint(sz[0] - 2 * m[2] / 3, m[3] + (i + 0.5) * length, 0)
			sketch.create_text(tkp, str(i+1), 0.005)
			if i > 0:
				pnt1 = sketch.create_keypoint(m[0], m[3] + i * length, 0)
				pnt2 = sketch.create_keypoint(m[0] / 2, m[3] + i * length, 0)
				sketch.create_line_edge(pnt1, pnt2).style_name = "border"
				pnt1 = sketch.create_keypoint(sz[0] - m[2], m[3] + i * length, 0)
				pnt2 = sketch.create_keypoint(sz[0] - m[2] / 2, m[3] + i * length, 0)
				sketch.create_line_edge(pnt1, pnt2).style_name = "border"

	def create_sketch_view(self, sketch, scale, offset):
		view = SketchView(self, sketch, scale, offset)
		self.changed(ChangeEvent(self, ChangeEvent.BeforeObjectAdded, view))
		self._views.append(view)
		self.changed(ChangeEvent(self, ChangeEvent.ObjectAdded, view))
		view.add_change_handler(self.on_view_changed)
		return view

	def create_part_view(self, part, scale, offset):
		view = PartView(self, part, scale, offset)
		self.changed(ChangeEvent(self, ChangeEvent.BeforeObjectAdded, view))
		self._views.append(view)
		self.changed(ChangeEvent(self, ChangeEvent.ObjectAdded, view))
		view.add_change_handler(self.on_view_changed)
		return view

	def delete(self):
		self.changed(ChangeEvent(self, ChangeEvent.Deleted, self))

	def add_field(self, name, value):
		field = Field(name, value)
		self.changed(ChangeEvent(self, ChangeEvent.BeforeObjectAdded, field))
		self._fields[name] = field
		self.changed(ChangeEvent(self, ChangeEvent.ObjectAdded, field))
		field.add_change_handler(self.on_field_changed)

	def get_field(self, name):
		if name in self._fields:
			return self._fields[name]
		else:
			return None

	def get_fields(self):
		return dict(self._fields)

	def get_views(self):
		return list(self._views)

	def on_view_changed(self, event):
		self.changed(ChangeEvent(self, ChangeEvent.ObjectChanged, event.sender))
		if event.type == ChangeEvent.Deleted:
			self.changed(ChangeEvent(self, ChangeEvent.BeforeObjectRemoved, event.sender))
			self._views.remove(event.sender)
			self.changed(ChangeEvent(self, ChangeEvent.ObjectRemoved, event.sender))
			event.sender.remove_change_handler(self.on_view_changed)

	def on_field_changed(self, event):
		self.changed(ChangeEvent(self, ChangeEvent.ObjectChanged, event.sender))
		if event.object == "name":
			self._fields.pop(event.old_value)
			self._fields[event.sender.name] = event.sender

	def serialize_json(self):
		print("test")
		return {
			'paper': Paper.serialize_json(self),
			'name': self._name,
			'views': self._views,
			'border_sketch': self._border_sketch,
			'header_sketch': self._header_sketch.uid,
			'fields': self._fields
		}

	@staticmethod
	def deserialize(data, document):
		drawing = Drawing(document)
		if data is not None:
			drawing.deserialize_data(data)
		return drawing

	def deserialize_data(self, data):
		Paper.deserialize_data(self, data['paper'])
		self._name = data.get('name', "No name")

		self._header_sketch = self._doc.get_geometries().get_geometry(data['header_sketch'])
		for field_data_tuple in data.get('fields', {}).items():
			field_data = field_data_tuple[1]
			field = Field.deserialize(field_data)
			self._fields[field.name] = field
			field.add_change_handler(self.on_field_changed)
		for view_data in data.get('views', []):
			if view_data['view']['type'] == ViewType.SketchView.value:
				view = SketchView.deserialize(view_data, self)
				self._views.append(view)
				view.add_change_handler(self.on_view_changed)
			if view_data['view']['type'] == ViewType.PartView.value:
				view = PartView.deserialize(view_data, self)
				self._views.append(view)
				view.add_change_handler(self.on_view_changed)
		self.generate_border()
Data/Areas.py
class Area(IdObject, NamedObservableObject):
	def __init__(self, sketch):
		IdObject.__init__(self)
		NamedObservableObject.__init__(self, "New Area")
		self._sketch = sketch
		self._brush = None
		self._brush_rotation = 50
		self._type = None

	@property
	def type(self):
		return self._type

	@property
	def brush(self):
		return self._brush

	@property
	def brush_name(self):
		if self._brush is None:
			return "None"
		else:
			return self._brush.name

	@property
	def brush_rotation(self):
		return self._brush_rotation

	@brush_rotation.setter
	def brush_rotation(self, value):
		self._brush_rotation = value
		self.changed(ChangeEvent(self, ChangeEvent.ValueChanged, {'name': 'brush_rotation'}))

	@brush_name.setter
	def brush_name(self, value):
		if value == "" or value is None or value == "None":
			self._brush = None
			self.changed(ChangeEvent(self, ChangeEvent.ValueChanged, {'name': 'brush_name'}))
			return
		styles = self._sketch.document.styles
		brush = styles.get_brush_by_name(value)
		self._brush = brush
		self.changed(ChangeEvent(self, ChangeEvent.ValueChanged, {'name': 'brush_name'}))

	@staticmethod
	def deserialize(data, sketch):
		if data['area']['type'] == AreaType.EdgeLoop.value:
			area = EdgeLoopArea(sketch)
			area.deserialize_data(data, sketch)
			return area
		elif data['area']['type'] == AreaType.Composite.value:
			area = CompositeArea(sketch)
			area.deserialize_data(data, sketch)
			return area

	def serialize_json(self):
		brush_uid = None
		if self._brush is not None:
			brush_uid = self._brush.uid
		return \
			{
				'uid': IdObject.serialize_json(self),
				'no': NamedObservableObject.serialize_json(self),
				'type': self._type.value,
				'brush': brush_uid,
				'brush_rot': self._brush_rotation
			}

	def deserialize_data(self, data):
		IdObject.deserialize_data(self, data['uid'])
		NamedObservableObject.deserialize_data(self, data.get('no', None))
		brush_uid = data.get('brush')
		self._brush = self._sketch.document.styles.get_brush(brush_uid)
		self._brush_rotation = data.get("brush_rot", 50)
Business/Undo.py
def on_redo(document):
	if len(document.redo_stack) > 0:
		do_object = document.redo_stack.pop()
		do_object.redo()
GUI/Widgets/PropertiesView.py
class PropertiesDock(QDockWidget):
	def __init__(self, main_window, doc: Document):
		QDockWidget.__init__(self, main_window)
		self.setWindowTitle("Properties")
		self.setObjectName("PropertiesDock")
		self._doc = doc
		self._table_view = QTableView(self)
		self.setWidget(self._table_view)
		self._model = PropertiesModel(doc)
		guiscale = gui_scale()
		self._table_view.setModel(self._model)
		self._table_view.setColumnWidth(0, 250 * guiscale)

		self._table_view.setItemDelegate(ComboboxDelegate(self))

	def set_item(self, item):
		self._model.set_item(item)
		self._table_view.resizeColumnsToContents()
Data/Parameters.py
class ParametersBase(NamedObservableObject):
	def __init__(self, name):
		NamedObservableObject.__init__(self, name)
Data/Feature.py
class FeatureType(Enum):
	ExtrudeFeature = 0
	RevolveFeature = 1
	FilletFeature = 2
	PlaneFeature = 3
	SketchFeature = 4
	NurbsSurfaceFeature = 5
GUI/Ribbon/RibbonWidget.py
class RibbonWidget(QTabWidget):
	def __init__(self, main_window):
		QWidget.__init__(self, main_window)

		self.setMaximumHeight(150 * gui_scale())
		self.setMinimumHeight(100 * gui_scale())
		self._tabs = {}

	def add_ribbon_tab(self, name):
		ribbon_tab = RibbonTab(self, tr(name, "ribbon"))
		self.addTab(ribbon_tab, name)
		self._tabs[name] = ribbon_tab
		return ribbon_tab

	def get_ribbon_tab(self, name):
		if name in self._tabs:
			return self._tabs[name]
		return self.add_ribbon_tab(name)
Data/Sketch.py
class Sketch(Geometry):
	def __init__(self, params_parent):
		Geometry.__init__(self, params_parent, "New Sketch", Geometry.Sketch)
		self._key_points = {}
		self._edges = {}
		self._texts = {}
		self._areas = {}
		self._sketch_instances = {}
		self._proformers = {}
		self.threshold = 0.1
		self.edge_naming_index = 1

	def add_edge(self, edge):
		self.changed(ChangeEvent(self, ChangeEvent.BeforeObjectAdded, edge))
		self._edges[edge.uid] = edge
		self.changed(ChangeEvent(self, ChangeEvent.ObjectAdded, edge))
		edge.add_change_handler(self.on_edge_changed)

	def clear(self):
		kps = list(self._key_points.values())
		for kp in kps:
			kp.delete()
		self._key_points.clear()
		self._edges.clear()
		self._areas.clear()
		self.edge_naming_index = 1
		self.changed(ChangeEvent(self, ChangeEvent.Cleared, self))

	def get_limits(self):
		limits = [1.0e16, 1.0e16, -1.0e16, -1.0e16]
		kps = self.get_keypoints()
		for pnt in kps:
			limits[0] = min(pnt.x, limits[0])
			limits[1] = min(pnt.y, limits[1])
			limits[2] = max(pnt.x, limits[2])
			limits[3] = max(pnt.y, limits[3])

		for edge in self.get_edges():
			if edge.type == EdgeType.CircleEdge:
				ckp = edge.get_keypoints()[0]
				r = edge.get_meta_data("r")
				limits[0] = min(ckp.x-r, limits[0])
				limits[1] = min(ckp.y-r, limits[1])
				limits[2] = max(ckp.x+r, limits[2])
				limits[3] = max(ckp.y+r, limits[3])

		for instance in self._sketch_instances.values():
			in_lim = instance.sketch.get_limits()
			sc = instance.scale
			limits[0] = min(in_lim[0]*sc + instance.offset.x, limits[0])
			limits[1] = min(in_lim[1]*sc + instance.offset.y, limits[1])
			limits[2] = max(in_lim[2]*sc + instance.offset.x, limits[2])
			limits[3] = max(in_lim[3]*sc + instance.offset.y, limits[3])
		return limits

	@property
	def key_point_count(self):
		return len(self.get_keypoints())

	@property
	def edges_count(self):
		return len(self.get_edges())

	@property
	def sketch_instances(self):
		return self._sketch_instances.values()

	@property
	def proformers(self):
		return self._proformers.values()

	def get_sketch(self, uid):
		return self.parent.get_sketch(uid)

	def get_edge(self, uid):
		if uid in self._edges:
			return self._edges[uid]
		else:
			for proformer in self._proformers.values():
				edge = proformer.get_edge(uid)
				if edge is not None:
					return edge
		return None

	def get_edge_by_name(self, name):
		for edge in self._edges.values():
			if edge.name == name:
				return edge
		for proformer in self._proformers.values():
			for edge in proformer.result_edges:
				if edge.name == name:
					return edge
		return None

	def get_keypoint(self, uid):
		if uid in self._key_points:
			return self._key_points[uid]
		else:
			for proformer in self._proformers.values():
				kp = proformer.get_keypoint(uid)
				if kp is not None:
					return kp
		return None

	def get_area(self, uid):
		if uid in self._areas:
			return self._areas[uid]
		else:
			for proformer in self._proformers.values():
				area = proformer.get_area(uid)
				if area is not None:
					return area
		return None

	def get_edges(self):
		edges = []
		edges.extend(self._edges.values())
		for proformer in self._proformers.values():
			edges.extend(proformer.result_edges)
		return edges

	def get_keypoints(self):
		kps = []
		kps.extend(self._key_points.values())
		for proformer in self._proformers.values():
			kps.extend(proformer.result_keypoints)
		return kps

	def get_texts(self):
		return self._texts.values()

	def get_areas(self):
		areas = []
		areas.extend(self._areas.values())
		for proformer in self._proformers.values():
			areas.extend(proformer.result_areas)
		return areas

	def get_fillet(self, uid):
		if uid in self._fillets:
			return self._fillets[uid]
		else:
			return None

	def get_parent(self):
		return self._parent

	def get_next_edge_naming_index(self):
		self.edge_naming_index += 1
		return self.edge_naming_index - 1

	def remove_areas(self, areas):
		for area in areas:
			if area.uid in self._areas:
				self.changed(ChangeEvent(self, ChangeEvent.BeforeObjectRemoved, area))
				self._areas.pop(area.uid)
				area.changed(ChangeEvent(self, ChangeEvent.Deleted, area))
				self.changed(ChangeEvent(self, ChangeEvent.ObjectRemoved, area))

	def remove_edge(self, edge):
		if edge.uid in self._edges:
			self.changed(ChangeEvent(self, ChangeEvent.BeforeObjectRemoved, edge))
			self._edges.pop(edge.uid)
			edge.changed(ChangeEvent(self, ChangeEvent.Deleted, edge))
			self.changed(ChangeEvent(self, ChangeEvent.ObjectRemoved, edge))

	def remove_edges(self, edges):
		for edge in edges:
			self.remove_edge(edge)

	def remove_key_points(self, key_points):
		for kp in key_points:
			if kp.uid in self._key_points:
				kp.changed(ChangeEvent(self, ChangeEvent.Deleted, kp))
				self._key_points.pop(kp.uid)

	def get_keypoint_by_location(self, x, y, z, ts=None):
		key_point = None
		for p_tuple in self._key_points.items():
			p = p_tuple[1]
			if ts is None:
				ts = self.threshold
			if abs(p.x - x) < ts and abs(p.y - y) < ts and abs(p.z - z) < ts:
				key_point = p
				break
		return key_point

	def create_keypoint(self, x, y, z):
		key_point = KeyPoint(self, x, y, z)
		self.add_keypoint(key_point)
		return key_point

	def add_keypoint(self, key_point):
		self.changed(ChangeEvent(self, ChangeEvent.BeforeObjectAdded, key_point))
		self._key_points[key_point.uid] = key_point
		self.changed(ChangeEvent(self, ChangeEvent.ObjectAdded, key_point))
		key_point.add_change_handler(self.on_kp_changed)

	def create_proformer(self, type, name):
		proformer = Proformer(self, type, name)
		self.changed(ChangeEvent(self, ChangeEvent.BeforeObjectAdded, proformer))
		self._proformers[proformer.uid] = proformer
		self.changed(ChangeEvent(self, ChangeEvent.ObjectAdded, proformer))
		proformer.add_change_handler(self.on_proformer_changed)
		return proformer

	def create_sketch_instance(self, sketch_to_insert, kp):
		sketch_instance = SketchInstance(self, sketch_to_insert.name)
		sketch_instance.sketch = sketch_to_insert
		sketch_instance.offset = kp
		self.changed(ChangeEvent(self, ChangeEvent.BeforeObjectAdded, sketch_instance))
		self._sketch_instances[sketch_instance.uid] = sketch_instance
		self.changed(ChangeEvent(self, ChangeEvent.ObjectAdded, sketch_instance))
		return sketch_instance

	def create_circle_edge(self, kp, radius_param):
		circle_edge = None
		if kp.uid in self._key_points:
			circle_edge = Edge(self, EdgeType.CircleEdge)
			circle_edge.name = "Edge" + str(self.edge_naming_index)
			circle_edge.add_key_point(kp)
			circle_edge.set_meta_data('r', radius_param.value)
			circle_edge.set_meta_data_parameter('r', radius_param)
			self.edge_naming_index += 1
			self.changed(ChangeEvent(self, ChangeEvent.BeforeObjectAdded, circle_edge))
			self._edges[circle_edge.uid] = circle_edge
			self.changed(ChangeEvent(self, ChangeEvent.ObjectAdded, circle_edge))
			circle_edge.add_change_handler(self.on_edge_changed)
		return circle_edge

	def create_fillet_edge(self, kp, radius_param):
		fillet_edge = None

		if kp in self.get_keypoints():
			fillet_edge = Edge(self, EdgeType.FilletLineEdge)
			fillet_edge.name = "Edge" + str(self.edge_naming_index)
			fillet_edge.add_key_point(kp)
			fillet_edge.set_meta_data('r', radius_param.value)
			fillet_edge.set_meta_data_parameter('r', radius_param)
			self.edge_naming_index += 1
			self.changed(ChangeEvent(self, ChangeEvent.BeforeObjectAdded, fillet_edge))
			self._edges[fillet_edge.uid] = fillet_edge
			self.changed(ChangeEvent(self, ChangeEvent.ObjectAdded, fillet_edge))
			fillet_edge.add_change_handler(self.on_edge_changed)
			for area_tuple in self._areas.items():
				area = area_tuple[1]
				if area.type == AreaType.EdgeLoop:
					kps = area.get_keypoints()
					if kp in kps:
						area.add_edge(fillet_edge)
		return fillet_edge

	def create_nurbs_edge(self, kp):
		nurbs_edge = None
		if kp.uid in self._key_points:
			nurbs_edge = Edge(self, EdgeType.NurbsEdge)
			nurbs_edge.name = "Edge" + str(self.edge_naming_index)
			nurbs_edge.add_key_point(kp)
			nurbs_edge.set_meta_data("n", 2)
			self.edge_naming_index += 1
			self.changed(ChangeEvent(self, ChangeEvent.BeforeObjectAdded, nurbs_edge))
			self._edges[nurbs_edge.uid] = nurbs_edge
			self.changed(ChangeEvent(self, ChangeEvent.ObjectAdded, nurbs_edge))
			nurbs_edge.add_change_handler(self.on_edge_changed)
		return nurbs_edge

	def create_text(self, key_point, value, height):
		text = Text(self)
		text.key_point = key_point
		text.value = value
		text.height = height
		self.changed(ChangeEvent(self, ChangeEvent.BeforeObjectAdded, text))
		self._texts[text.uid] = text
		self.changed(ChangeEvent(self, ChangeEvent.ObjectAdded, text))
		text.add_change_handler(self.on_text_changed)
		return text

	def create_attribute(self, kp, name, default_value, height):
		attribute = Attribute(self)
		attribute.key_point = kp
		attribute.name = name
		attribute.value = default_value
		attribute.height = height
		self.changed(ChangeEvent(self, ChangeEvent.BeforeObjectAdded, attribute))
		self._texts[attribute.uid] = attribute
		self.changed(ChangeEvent(self, ChangeEvent.ObjectAdded, attribute))
		attribute.add_change_handler(self.on_text_changed)
		return attribute

	def create_line_edge(self, key_point1, key_point2):
		line_edge = Edge(self, EdgeType.LineEdge)
		line_edge.name = "Edge" + str(self.edge_naming_index)
		self.edge_naming_index += 1
		line_edge.add_key_point(key_point1)
		line_edge.add_key_point(key_point2)
		self.add_edge(line_edge)
		return line_edge

	def create_arc_edge(self, center_key_point, start_angle, end_angle, radius):
		arc_edge = Edge(self, EdgeType.ArcEdge)
		arc_edge.name = "Edge" + str(self.edge_naming_index)
		self.edge_naming_index += 1
		arc_edge.add_key_point(center_key_point)
		arc_edge.set_meta_data("sa", start_angle)
		arc_edge.set_meta_data("ea", end_angle)
		arc_edge.set_meta_data("r", radius)
		self.changed(ChangeEvent(self, ChangeEvent.BeforeObjectAdded, arc_edge))
		self._edges[arc_edge.uid] = arc_edge
		self.changed(ChangeEvent(self, ChangeEvent.ObjectAdded, arc_edge))
		arc_edge.add_change_handler(self.on_edge_changed)
		return arc_edge

	def get_new_unique_area_name(self, name):
		unique = False
		counter = 1
		new_name = name
		while not unique:
			found = False
			for area_tuple in self._areas.items():
				if area_tuple[1].name == new_name:
					found = True
					new_name = name + str(counter)
					counter += 1
			if not found:
				unique = True
		return new_name

	def create_area(self):
		area = EdgeLoopArea(self)
		self.changed(ChangeEvent(self, ChangeEvent.BeforeObjectAdded, area))
		self._areas[area.uid] = area
		area.name = self.get_new_unique_area_name("New Area")
		self.changed(ChangeEvent(self, ChangeEvent.ObjectAdded, area))
		area.add_change_handler(self.on_area_changed)
		return area

	def create_composite_area(self):
		area = CompositeArea(self)
		self.changed(ChangeEvent(self, ChangeEvent.BeforeObjectAdded, area))
		self._areas[area.uid] = area
		area.name = self.get_new_unique_area_name("New Area")
		self.changed(ChangeEvent(self, ChangeEvent.ObjectAdded, area))
		area.add_change_handler(self.on_area_changed)
		return area



	def clear_areas(self):
		areas_to_delete = []
		for area_tuple in self._areas.items():
			areas_to_delete.append(area_tuple[1])

		for area in areas_to_delete:
			area.delete()

	def on_kp_changed(self, event):
		self.changed(ChangeEvent(self, ChangeEvent.ObjectChanged, event.sender))
		if event.type == ChangeEvent.Deleted:
			if event.sender.uid in self._key_points:
				self.changed(ChangeEvent(self, ChangeEvent.BeforeObjectRemoved, event.sender))
				self._key_points.pop(event.sender.uid)
				self.changed(ChangeEvent(self, ChangeEvent.ObjectRemoved, event.sender))
			event.object.remove_change_handler(self.on_kp_changed)

	def on_text_changed(self, event):
		self.changed(ChangeEvent(self, ChangeEvent.ObjectChanged, event.sender))
		if event.type == ChangeEvent.Deleted:
			if event.object.uid in self._texts:
				self.changed(ChangeEvent(self, ChangeEvent.BeforeObjectRemoved, event.sender))
				self._texts.pop(event.object.uid)
				self.changed(ChangeEvent(self, ChangeEvent.ObjectRemoved, event.sender))
			event.object.remove_change_handler(self.on_text_changed)

	def on_edge_changed(self, event):
		if event.type == ChangeEvent.Deleted:
			if event.object.uid in self._edges:
				self.changed(ChangeEvent(self, ChangeEvent.BeforeObjectRemoved, event.sender))
				self._edges.pop(event.object.uid)
				self.changed(ChangeEvent(self, ChangeEvent.ObjectRemoved, event.sender))
			event.object.remove_change_handler(self.on_edge_changed)

	def on_area_changed(self, event: ChangeEvent):
		if event.type == ChangeEvent.Deleted:
			if event.object.uid in self._areas:
				self.changed(ChangeEvent(self, ChangeEvent.BeforeObjectRemoved, event.sender))
				self._areas.pop(event.object.uid)
				self.changed(ChangeEvent(self, ChangeEvent.ObjectRemoved, event.sender))
			event.object.remove_change_handler(self.on_area_changed)

	def on_sketch_instance_changed(self, event: ChangeEvent):
		if event.type == ChangeEvent.Deleted:
			if event.object.uid in self._sketch_instances:
				self.changed(ChangeEvent(self, ChangeEvent.BeforeObjectRemoved, event.sender))
				self._sketch_instances.pop(event.object.uid)
				self.changed(ChangeEvent(self, ChangeEvent.ObjectRemoved, event.sender))
			event.object.remove_change_handler(self.on_sketch_instance_changed)

	def on_proformer_changed(self, event: ChangeEvent):
		if event.type == ChangeEvent.Deleted:
			if event.object.uid in self._proformers:
				self.changed(ChangeEvent(self, ChangeEvent.BeforeObjectRemoved, event.sender))
				self._proformers.pop(event.object.uid)
				self.changed(ChangeEvent(self, ChangeEvent.ObjectRemoved, event.sender))
			event.object.remove_change_handler(self.on_proformer_changed)

	def delete(self):
		self.changed(ChangeEvent(self, ChangeEvent.Deleted, self))

	def serialize_json(self):
		return {
			'uid': IdObject.serialize_json(self),
			'parameters': Parameters.serialize_json(self),
			'key_points': self._key_points,
			'edges': self._edges,
			'areas': self._areas,
			'texts': self._texts,
			'instances': self._sketch_instances,
			'proformers': self._proformers,
			'threshold': self.threshold,
			'edge_naming_index': self.edge_naming_index,
			'type': self._geometry_type
		}

	@staticmethod
	def deserialize(data, param_parent):
		sketch = Sketch(param_parent)
		if data is not None:
			sketch.deserialize_data(data)
		return sketch

	def deserialize_data(self, data):
		IdObject.deserialize_data(self, data['uid'])
		Parameters.deserialize_data(self, data['parameters'])
		self.threshold = data.get('threshold', 0.1)
		self.edge_naming_index = data.get('edge_naming_index', 0)
		for kp_data_tuple in data.get('key_points', {}).items():
			kp_data = kp_data_tuple[1]
			kp = KeyPoint.deserialize(kp_data, self)
			self._key_points[kp.uid] = kp
			kp.add_change_handler(self.on_kp_changed)
		for edge_data_tuple in data.get('edges', {}).items():
			edge_data = edge_data_tuple[1]
			edge = Edge.deserialize(self, edge_data)
			self._edges[edge.uid] = edge
			edge.add_change_handler(self.on_edge_changed)
		for text_data_tuple in data.get('texts', {}).items():
			text_data = text_data_tuple[1]
			text_type = text_data.get('type', Text.SimpleTextType)
			if text_type == Text.SimpleTextType:
				text = Text.deserialize(self, text_data)
			elif text_type == Text.AttributeType:
				text = Attribute.deserialize(self, text_data)
			self._texts[text.uid] = text
			text.add_change_handler(self.on_text_changed)

		for area_data_tuple in data.get('areas', {}).items():
			area_data = area_data_tuple[1]
			area = Area.deserialize(area_data, self)
			self._areas[area.uid] = area
			area.add_change_handler(self.on_area_changed)

		for sketch_instance_tuple in data.get('instances', {}).items():
			sketch_instance_data = sketch_instance_tuple[1]
			sketch_instance = SketchInstance.deserialize(self, sketch_instance_data)
			self._sketch_instances[sketch_instance.uid] = sketch_instance
			sketch_instance.add_change_handler(self.on_sketch_instance_changed)

		for proformer_data in data.get('proformers', {}).values():
			proformer = Proformer.deserialize(self, proformer_data)
			self._proformers[proformer.uid] = proformer
			proformer.add_change_handler(self.on_proformer_changed)

		if self.edge_naming_index == 0:
			for edge_tuple in self._edges.items():
				edge = edge_tuple[1]
				if "Edge" in edge.name:
					try:
						index = int(edge.name.replace("Edge", ""))
						self.edge_naming_index = max(self.edge_naming_index, index)
					except ValueError:
						pass
			self.edge_naming_index += 1
GUI/Widgets/TreeView.py
class TreeViewDock(QDockWidget):
	def __init__(self, main_window, document: Document):
		QDockWidget.__init__(self, main_window)
		self._main_window = main_window
		self.setWindowTitle(tr("Project view"))
		self.setObjectName("TreeViewDock")
		self._doc = document
		self._treeView = QTreeView(self)
		self.setWidget(self._treeView)
		self._model = DocumentItemModel(document)
		self._treeView.setModel(self._model)
		self._treeView.setRootIndex(self._model.index(0, 0))
		self._treeView.selectionModel().selectionChanged.connect(self.on_tree_selection_changed)
		self._model.add_new_item_added_listener(self.on_new_item_added)
		self.installEventFilter(self)

	def on_delete(self):
		txt = tr("Are you sure you want to delete this item?", "messages")
		ret = QMessageBox.warning(self, tr("Delete item?"), txt, QMessageBox.Yes | QMessageBox.Cancel)
		if ret == QMessageBox.Yes:
			selection_model = self._treeView.selectionModel()
			indexes = selection_model.selectedIndexes()
			selected_items = set()
			for index in indexes:
				selected_items.add(index.internalPointer())
			delete_items(self._doc, selected_items)

	def eventFilter(self, obj, event):
		if event.type() == QEvent.KeyPress:
			if event.key() == Qt.Key_Delete:
				self.on_delete()
				return True
		return False

	def on_tree_selection_changed(self, selection):
		selection_model = self._treeView.selectionModel()
		indexes = selection_model.selectedIndexes()
		selected_items = []
		for index in indexes:
			selected_items.append(index.internalPointer().data)
		self._main_window.on_tree_selection_changed(selected_items)

	def on_new_item_added(self, index):
		select = False
		if type(index.internalPointer().data) is Sketch:
			select = True
		if type(index.internalPointer().data) is Drawing:
			select = True
		if type(index.internalPointer().data) is Part:
			select = True
		if type(index.internalPointer().data) is CalcTableAnalysis:
			select = True
		if type(index.internalPointer().data) is Geometry:
			select = True
		if select:
			sel_type = QItemSelectionModel.ClearAndSelect
			parent_index = self._model.parent(index)
			while parent_index.isValid():
				self._treeView.setExpanded(parent_index, True)
				parent_index = self._model.parent(parent_index)
			self._treeView.scrollTo(index)
			self._treeView.selectionModel().select(index, sel_type)
			parent_index = self._model.parent(index)
			while parent_index.isValid():
				self._treeView.setExpanded(parent_index, True)
				parent_index = self._model.parent(parent_index)
		self._main_window.on_new_item_added_in_tree(index.internalPointer().data)
GUI/Widgets/GeometryView.py
class GeometryDock(QDockWidget):
	def __init__(self, main_window, doc: Document):
		QDockWidget.__init__(self, main_window)
		self._main_window = main_window
		self._doc = doc
		self._widget = QWidget(self)
		self.setWidget(self._widget)
		guiscale = gui_scale()
		self.setWindowTitle("Geometry")
		self.setObjectName("geometryDock")
		self.setMinimumWidth(200 * guiscale)
		layout = QVBoxLayout()
		self._widget.setLayout(layout)
		layout.setContentsMargins(QMargins(0, 0, 0, 0))

		self.areas_table_widget = AreaTableView(self, doc, main_window)
		layout.addWidget(self.areas_table_widget)

		self.edges_table_widget = EdgesTableView(self, doc, main_window)
		layout.addWidget(self.edges_table_widget)

		self.kps_table_widget = KeyPointsTableView(self, doc, main_window)
		layout.addWidget(self.kps_table_widget)

	def set_sketch(self, sketch):
		self.areas_table_widget.set_sketch(sketch)
		self.edges_table_widget.set_sketch(sketch)
		self.kps_table_widget.set_sketch(sketch)

	def set_list(self, list):
		pass

	def on_edge_selection_changed(self, selected_edges):
		self.edges_table_widget.set_selected_edges(selected_edges)
		kps = []
		for edge in selected_edges:
			for kp in edge.get_end_key_points():
				kps.append(kp)
		if len(kps) > 0:
			self.kps_table_widget.hide_unselected_key_points = True
			self.kps_table_widget.set_selected_kps(kps)

	def on_kp_selection_changed(self, selected_key_points):
		self.kps_table_widget.hide_unselected_key_points = False
		self.kps_table_widget.set_selected_kps(selected_key_points)

	def on_area_selection_changed(self, selected_areas):
		self.areas_table_widget.set_selected_areas(selected_areas)
Data/Parameters.py
class Parameters(ParametersBase):
	def __init__(self, name, parent=None):
		ParametersBase.__init__(self, name)
		self._parameter_list = []
		self._params = {}
		self._parent = parent
		self._custom_name_getter = None
		self._standards = {}
		self._current_standard_name = "Normal"
		self._current_type_name = "Default"
		self._current_type = self.make_type(self._current_standard_name, self._current_type_name)

	@property
	def document(self):
		if self._parent is None:
			return self
		else:
			return self._parent.document

	@property
	def parent(self):
		return self._parent

	def param_in_current_type(self, param):
		if self._current_type is None:
			return False
		return param.uid in self._current_type

	def make_standard(self, name):
		standard = {}
		self._standards[name] = standard
		return standard

	@property
	def standards(self):
		return list(self._standards.keys())

	@property
	def standard(self):
		return self._current_standard_name

	@standard.setter
	def standard(self, value):
		if value in self._standards:
			self._current_standard_name = value
			self._current_type_name = ""
			self._current_type = None

	@property
	def types(self):
		return self._standards[self._current_standard_name].keys()

	def get_types_from_standard(self, standard):
		if standard in self._standards:
			return list(self._standards[standard].keys())
		return []

	def make_type(self, standard_name, type_name):
		if standard_name not in self._standards:
			standard = {}
			self._standards[standard_name] = standard
		else:
			standard = self._standards[standard_name]
		type = {}
		standard[type_name] = type
		return type

	@property
	def type(self):
		return self._current_type_name

	@type.setter
	def type(self, type_name):
		if type_name in self._standards[self._current_standard_name]:
			self._current_type = self._standards[self._current_standard_name][type_name]
			self._current_type_name = type_name
			if self._current_type is None:
				return
			for param_definition_tuple in self._current_type.items():
				uid = param_definition_tuple[0]
				val = param_definition_tuple[1]
				if uid in self._params:
					param = self._params[uid]
					old_value = param.value
					param.internal_formula = val['if']
					param.internal_value = param.evaluate(None)
					change_object = {
						'new value': param.value,
						'old value': old_value,
						'instance': None
					}
					param.changed(ChangeEvent(param, ChangeEvent.ValueChanged, change_object))
			self.changed(ChangeEvent(self, ChangeEvent.ObjectChanged, self))

	def _add_parameter_object(self, param):
		param.add_change_handler(self.on_parameter_changed)
		self.changed(ChangeEvent(self, ChangeEvent.BeforeObjectAdded, param))
		self._params[param.uid] = param
		self._parameter_list.append(param.uid)
		self.changed(ChangeEvent(self, ChangeEvent.ObjectAdded, param))

	def _remove_parameter_object(self, uid):
		if uid in self._params:
			self._params.pop(uid)

	def get_parameter_by_uid(self, uid) -> Parameter:
		if uid in self._params:
			return self._params[uid]
		elif self._parent is not None:
			return self._parent.get_parameter_by_uid(uid)
		else:
			return None

	def get_parameter_by_name(self, name) -> Parameter:
		param = None
		for prm in self._params.values():
			if prm.name == name:
				param = prm
		if param is None and self._custom_name_getter is not None:
			param = self._custom_name_getter(name)
		if param is None and self._parent is not None:
			param = self._parent.get_parameter_by_name(name)

		return param

	def get_all_local_parameters(self):
		params = list(self._params.items())
		return params

	def get_all_parameters(self):
		params = list(self._params.values())
		if self._parent is not None:
			params.extend(self._parent.get_all_parameters())
		return params

	def create_parameter(self, name=None, value=0.0):
		if name is None:
			if self._parent is None:
				name = "Global" + str(len(self._parameter_list))
			else:
				name = self.name + str(len(self._parameter_list))
		param = Parameter(self, name, value)
		self._add_parameter_object(param)
		return param

	def delete_parameter(self, uid):
		param = self.get_parameter_by_uid(uid)
		if param is not None:
			param.remove_change_handler(self.on_parameter_changed)
			self.changed(ChangeEvent(self, ChangeEvent.BeforeObjectRemoved, param))
			self._parameter_list.remove(uid)
			self.changed(ChangeEvent(self, ChangeEvent.ObjectRemoved, param))
			self._remove_parameter_object(uid)
			param.changed(ChangeEvent(param, ChangeEvent.Deleted, param))

	def delete_parameters(self, params):
		for param in params:
			if param.uid in self._parameter_list:
				self.delete_parameter(param.uid)

	def on_parameter_changed(self, event):
		param = event.sender
		self.changed(ChangeEvent(self, event.type, event.sender))
		if self._current_type is not None and param.uid in self._params:
			if 'instance' in event.object and 'new formula' in event.object:
				if event.object['instance'] is None and event.object['new formula'] != event.object['old formula']:

					if param.uid in self._current_type:
						predef = self._current_type[param.uid]
					else:
						predef = {}
						self._current_type[param.uid] = predef
					predef['if'] = param.internal_formula
					predef['iv'] = param.internal_value

	def get_index_of(self, parameter):
		if parameter.uid in self._parameter_list:
			return self._parameter_list.index(parameter.uid)
		else:
			return -1

	@property
	def length(self):
		return len(self._parameter_list)

	@property
	def length_all(self):
		if self._parent is not None:
			return self._parent.length_all + self.length
		else:
			return self.length

	def get_parameter_item(self, index):
		if index >= self.length:
			return self._parent.get_parameter_item(index - self.length)
		else:
			uid = self._parameter_list[index]
		param = self.get_parameter_by_uid(uid)
		return param

	def serialize_json(self):
		return {
			'name': self._name,
			'params': self._params,
			'parameter_list': self._parameter_list,
			'pps': self._standards,
			'csn': self._current_standard_name,
			'ctn': self._current_type_name
		}

	@staticmethod
	def deserialize(data, parent):
		param = Parameters(parent)
		param.deserialize_data(data)
		return param

	def deserialize_data(self, data):
		self._parameter_list = data.get('parameter_list', [])
		self._name = data.get('name', 'name missing')
		self._standards = data.get('pps', self._standards)
		self._current_standard_name = data.get("csn", self._current_standard_name)
		self._current_type_name = data.get("ctn", self._current_type_name)
		self._current_type = self._standards[self._current_standard_name][self._current_type_name]
		for param_data in data.get('params', {}).items():
			param = Parameter.deserialize(param_data[1], self)
			self._params[param.uid] = param
			param.add_change_handler(self.on_parameter_changed)

		for param_tuple in self._params.items():
			if param_tuple[1].formula != '':
				try:
					param_tuple[1].value = param_tuple[1].formula
				except Exception as e:
					pass
GUI/Widgets/ParametersWidget.py
class ParametersWidget(QWidget):
	def __init__(self, main_window, document):
		QWidget.__init__(self, main_window)
		# self._document = document
		self._main_window = main_window
		guiscale = gui_scale()
		self._ignore_type_standard_change = False
		self._gui_scale = guiscale
		self._parameters = document.get_parameters()
		self.parameters_table = QTableView(self)
		self.parameters_model = ParametersModel(self._parameters)
		self.parameters_model.add_user_input_handler(self.on_user_input)
		self.parameters_sort_model = QSortFilterProxyModel()
		self.parameters_sort_model.setSourceModel(self.parameters_model)
		self.parameters_table.setModel(self.parameters_sort_model)
		self.parameters_table.setSortingEnabled(True)
		self.parameters_sort_model.setSortCaseSensitivity(False)
		layout = QVBoxLayout()
		self.setLayout(layout)
		predefined_standards_widget = QWidget(self)
		predefined_standards_widget.setLayout(QHBoxLayout())
		predefined_standards_widget.layout().addWidget(QLabel("Standard"))
		self._standards_combobox = QComboBox(predefined_standards_widget)
		self._standards_combobox.setMinimumWidth(110*guiscale)
		predefined_standards_widget.layout().addWidget(self._standards_combobox)
		predefined_standards_widget.layout().addWidget(QLabel("Type"))
		self._type_combobox = QComboBox(predefined_standards_widget)
		predefined_standards_widget.layout().addWidget(self._type_combobox)
		self._type_combobox.setMinimumWidth(110*guiscale)
		self._standards_combobox.currentTextChanged.connect(self.on_standard_changed)
		self._type_combobox.currentTextChanged.connect(self.on_type_changed)
		add_type_button = QPushButton("Manage")
		add_type_button.clicked.connect(self.on_manage)
		predefined_standards_widget.layout().layout().addWidget(add_type_button)

		layout.addWidget(predefined_standards_widget)
		layout.addWidget(self.parameters_table)
		layout.setContentsMargins(QMargins(0, 0, 0, 0))
		self.parameters_table.setColumnWidth(0, 120 * guiscale)
		self.parameters_table.setColumnWidth(1, 150 * guiscale)
		self.parameters_table.setColumnWidth(2, 80 * guiscale)
		self.parameters_table.setColumnWidth(3, 40 * guiscale)
		buttons_widget = QWidget()
		buttons_widget.setLayout(QHBoxLayout())
		add_button = QPushButton("Add Parameter")
		add_button.clicked.connect(self.on_add_parameter)
		buttons_widget.layout().addWidget(add_button)
		delete_button = QPushButton("Delete Parameter")
		buttons_widget.layout().addWidget(delete_button)
		delete_button.clicked.connect(self.on_delete)
		governor_button = QPushButton("Set governor")
		governor_button.clicked.connect(self.on_set_governor)
		buttons_widget.layout().addWidget(governor_button)
		layout.addWidget(buttons_widget)
		self.installEventFilter(self)
		# self.update_hide_parameters()
		self.parameters_table.selectionModel().selectionChanged.connect(self.on_param_selection_changed)

	def set_parameters(self, params):
		if params == self._parameters:
			return
		self._ignore_type_standard_change = True
		if self._parameters is not None:
			self._parameters.remove_change_handler(self.on_parameters_changed)
		self._parameters = params
		self._parameters.add_change_handler(self.on_parameters_changed)
		self.parameters_model.set_parameters(params)
		self.parameters_table.resizeColumnsToContents()
		self.update_standards(params)
		self.update_types(params)
		self.update_hide_parameters()
		self._ignore_type_standard_change = False

	def update_standards(self, params):
		self._standards_combobox.clear()
		options = list(params.standards)
		self._standards_combobox.addItems(options)
		if params.standard in options:
			self._standards_combobox.setCurrentIndex(options.index(params.standard))


	def update_types(self, params):
		self._type_combobox.clear()
		options = list(params.get_types_from_standard(params.standard))
		self._type_combobox.addItems(options)
		if params.type != "" and params.type in options:
			self._type_combobox.setCurrentIndex(options.index(params.type))

	def on_parameters_changed(self, event):
		if event.type == event.ObjectAdded or event.type == event.ObjectRemoved:
			self.update_hide_parameters()

	def on_manage(self):
		std = StandardTypeDialog(self, self._parameters)
		std.exec_()
		self.update_standards(self._parameters)
		self.update_types(self._parameters)
		# self._ignore_type_standard_change = True
		# self._parameters.make_type(self._parameters.standard, "New Type")
		# self._type_combobox.clear()
		# options = list(self._parameters.types)
		# self._type_combobox.addItems(options)
		# self._type_combobox.setCurrentIndex(options.index(self._parameters.type))
		# self._ignore_type_standard_change = False

	def on_type_changed(self, value):
		if not self._ignore_type_standard_change:
			self._parameters.type = value
			self.on_user_input(None)

	def on_standard_changed(self, value):
		if not self._ignore_type_standard_change:
			self._parameters.standard = value
			self.update_types(self._parameters)

	def on_add_parameter(self):
		# self.parent().parent().on_add_parameter()
		add_parameter(self._parameters)
		self.update_hide_parameters()
		ndx = self.parameters_model.index(self.parameters_model.rowCount() - 1, 0)
		index = self.parameters_sort_model.mapFromSource(ndx)
		self.parameters_table.scrollTo(index)
		sm = self.parameters_table.selectionModel()
		sm.setCurrentIndex(index, QItemSelectionModel.ClearAndSelect | QItemSelectionModel.Rows)

	def on_set_governor(self):
		rows = self.parameters_table.selectionModel().selectedRows()
		params = []
		for param in self._parameters.get_all_parameters():
			params.append(param.name)
		params.sort()
		value = QInputDialog.getItem(self, "Set parameter", "Parameter:", params, 0, False)
		governor_parameter = self._parameters.get_parameter_by_name(value[0])
		for ndx in rows:
			index = self.parameters_sort_model.mapToSource(ndx)
			row = index.row()
			param = self._parameters.get_parameter_item(row)
			if param is not governor_parameter:
				param.value = (governor_parameter.name + "+" + str(round(param.value - governor_parameter.value, 4))).replace("+-", '-')

	def on_delete(self):
		txt = "Are you sure you want to delete this parameter?"
		ret = QMessageBox.warning(self, "Delete parameter?", txt, QMessageBox.Yes | QMessageBox.Cancel)
		if ret == QMessageBox.Yes:
			selection_model = self.parameters_table.selectionModel()
			indexes = selection_model.selectedIndexes()
			selected_rows = set()
			for index in indexes:
				index = self.parameters_sort_model.mapToSource(index)
				selected_rows.add(index.row())
			self.parameters_model.remove_rows(selected_rows)

	def eventFilter(self, obj, event):
		if event.type() == QEvent.KeyPress:
			if event.key() == Qt.Key_Delete:
				self.on_delete()
				return True
		return False

	def update_hide_parameters(self):
		rowcount = self.parameters_sort_model.rowCount()
		for i in range(rowcount):
			index = self.parameters_model.index(i, 0)
			index = self.parameters_sort_model.mapFromSource(index)
			row = index.row()
			if not self._main_window.states.show_params:
				hide = self.parameters_model.row_hidden(i)
				if hide:
					self.parameters_table.hideRow(row)
				else:
					self.parameters_table.showRow(row)
			else:
				self.parameters_table.showRow(i)

	def on_param_selection_changed(self, selection):
		selection_model = self.parameters_table.selectionModel()
		indexes = selection_model.selectedIndexes()
		selected_parameters = set()
		for index in indexes:
			ndx = self.parameters_sort_model.mapToSource(index)
			selected_parameters.add(self.parameters_model.get_parameter_from_row(ndx.row()))
		self._main_window.on_param_selection_changed_in_parameters_widget(list(selected_parameters))

	def on_user_input(self, event):
		self._main_window.on_update_view()
GUI/Ribbon/RibbonButton.py
class RibbonButton(QToolButton):
	def __init__(self, owner, action, is_large):
		QPushButton.__init__(self, owner)
		# sc = 1
		sc = gui_scale()
		self.text = action.text()
		self._action = action
		self.textWidth = 50;

		self.clicked.connect(self._action.trigger)
		self._action.changed.connect(self.update_button_status_from_action)
		self.font = QFont()
		self.font.setPointSizeF(10*sc)
		fm = QFontMetrics(QFont())
		for t in self.text.split("\n"):
			self.textWidth = max(self.textWidth, fm.width(t)+10)

		if is_large:
			self.setStyleSheet(get_stylesheet("ribbonButton"))
			self.setToolButtonStyle(3)
			self.setIconSize(QSize(32 * sc, 32 * sc))
			self.setMaximumWidth(90 * sc)
			self.setMinimumWidth(50 * sc)
			self.setMinimumHeight(80 * sc)
			self.setMaximumHeight(95 * sc)
		else:
			self.setToolButtonStyle(2)
			self.setMaximumWidth(120 * sc)
			self.setIconSize(QSize(16 * sc, 16 * sc))
			self.setStyleSheet(get_stylesheet("ribbonSmallButton"))

		self.update_button_status_from_action()

	def paintEvent(self, event):
		if self.toolButtonStyle() == 3:
			qp = QPainter()
			qp.begin(self)
			qp.setFont(self.font)
			qp.drawText(QRectF(0, 32, self.textWidth,60), Qt.AlignHCenter | Qt.AlignVCenter, self.text)
			qp.end()

		return super(RibbonButton, self).paintEvent(event)

	def sizeHint(self):
		return QSize(self.textWidth, 85*gui_scale())

	def update_button_status_from_action(self):
		if self.toolButtonStyle() == 3:
			pass
		else:
			self.setText(self._action.text())
		self.setStatusTip(self._action.statusTip())
		self.setToolTip(self._action.toolTip())
		self.setIcon(self._action.icon())
		self.setEnabled(self._action.isEnabled())
		self.setCheckable(self._action.isCheckable())
		self.setChecked(self._action.isChecked())
Business/SketchActions.py
def create_add_sketch_to_parent(parent):
	sketch = Sketch(parent)
	parent.add_sketch(sketch)
	parent.document.undo_stack.append(CreateSketchDoObject(sketch, parent))
	return sketch
GUI/Icons.py
def get_icon(name):
	global icons_instance
	if not icons_instance:
		icons_instance = Icons()
	return icons_instance.icon(name)
Data/Edges.py
class Edge(IdObject, NamedObservableObject):
	def __init__(self, geometry, type=EdgeType.LineEdge, name="New Edge", plane=Plane()):
		IdObject.__init__(self)
		NamedObservableObject.__init__(self, name)
		self._type = type
		self._geometry = geometry
		self._key_points = []
		self._meta_data = {}
		self._meta_data_parameters = {}
		self._style = geometry.document.styles.get_edge_style_by_name('default')
		self._plane = plane
		self.editable = True
		self._draw_datas = {}
		self._change_events_initalized = True
		self._occluders = []

	@property
	def style(self):
		return self._style

	@property
	def type_name(self):
		return self._type.name

	@property
	def style_name(self):
		if self._style is None:
			return "No style"
		else:
			return self._style.name

	@style_name.setter
	def style_name(self, value):
		if value == "" or value is None or value == "No style":
			self._style = None
			return
		styles = self._geometry.document.styles
		edge_style = styles.get_edge_style_by_name(value)
		self._style = edge_style

	@property
	def plane(self):
		return self._plane

	@property
	def occluded(self):
		return len(self._occluders) > 0

	@property
	def geometry(self):
		return self._geometry

	def add_occluder(self, occluder):
		was_occluded = self.occluded
		self._occluders.append(occluder)
		if was_occluded != self.occluded:
			self.changed(ChangeEvent(self, ChangeEvent.HiddenChanged, self))

	def remove_occluder(self, occluder):
		if occluder in self._occluders:
			was_occluded = self.occluded
			self._occluders.remove(occluder)
			if was_occluded != self.occluded:
				self.changed(ChangeEvent(self, ChangeEvent.HiddenChanged, self))

	def delete(self):
		self.changed(ChangeEvent(self, ChangeEvent.Deleted, self))

	def set_meta_data(self, name, value):
		for param_tuple in self._meta_data_parameters.items():
			if param_tuple[1] == name:
				param = self._geometry.get_parameter_by_uid(param_tuple[0])
				param.value = value
				return
		self._meta_data[name] = value

	def get_meta_data(self, name, instance=None):
		if instance is None:
			return self._meta_data[name]
		else:
			param = self.get_meta_data_parameter(name)
			if param is None:
				return self._meta_data[name]
			else:
				return param.get_instance_value(instance)

	def get_meta_data_parameter(self, name):
		doc = self._geometry.document
		for param_tuple in self._meta_data_parameters.items():
			if param_tuple[1] == name:
				return self._geometry.get_parameter_by_uid(param_tuple[0])
		return None

	def set_meta_data_parameter(self, name, parameter):
		self._meta_data_parameters[parameter.uid] = name
		param = self._geometry.get_parameter_by_uid(parameter.uid)
		self._meta_data[name] = param.value
		param.add_change_handler(self.on_parameter_change)

	def on_parameter_change(self, event):
		param = event.sender
		uid = param.uid
		meta_name = self._meta_data_parameters[uid]
		self._meta_data[meta_name] = param.value
		if self._type == EdgeType.ArcEdge:
			ckp = self._geometry.get_keypoint(self._key_points[0])
			self.update_linked_kps(ckp)

	def get_keypoints(self):
		if not self._change_events_initalized:
			self.initialize_change_events()
		kps = []
		for uid in self._key_points:
			kps.append(self._geometry.get_keypoint(uid))
		return kps

	def get_key_point_uids(self):

		kps = []
		for uid in self._key_points:
			kps.append(uid)
		return kps

	def get_end_key_points(self):
		if self._type == EdgeType.LineEdge:
			return self.get_keypoints()
		if self._type == EdgeType.NurbsEdge:
			key_points = self.get_keypoints()
			kps = [key_points[0]]
			if len(key_points) > 1:
				kps.append(key_points[len(key_points) - 1])
			else:
				kps.append(key_points[0])
			return kps
		elif self._type == EdgeType.ArcEdge:
			ckp = self._geometry.get_keypoint(self._key_points[0])
			if 'start_kp' in self._meta_data:
				start_kp = self._geometry.get_keypoint(self._meta_data['start_kp'])
			else:
				pm = self._plane.get_global_projection_matrix()
				x = cos(self._meta_data['sa']) * self._meta_data['r']
				y = sin(self._meta_data['sa']) * self._meta_data['r']
				z = 0
				pc1 = ckp.xyz + pm.dot(np.array([x, y, z]))
				start_kp = self._geometry.create_keypoint(pc1[0], pc1[1], pc1[2])
				start_kp.add_edge(self)
				start_kp.add_change_handler(self.on_key_point_changed)
				start_kp.editable = False
				self._meta_data['start_kp'] = start_kp.uid
			if 'end_kp' in self._meta_data:
				end_kp = self._geometry.get_keypoint(self._meta_data['end_kp'])
			else:
				pm = self._plane.get_global_projection_matrix()
				x = cos(self._meta_data['ea']) * self._meta_data['r']
				y = sin(self._meta_data['ea']) * self._meta_data['r']
				z = 0
				pc1 = ckp.xyz + pm.dot(np.array([x, y, z]))
				end_kp = self._geometry.create_keypoint(pc1[0], pc1[1], pc1[2])
				end_kp.add_edge(self)
				end_kp.add_change_handler(self.on_key_point_changed)
				end_kp.editable = False
				self._meta_data['end_kp'] = end_kp.uid
			return [start_kp, end_kp]
		elif self.type == EdgeType.CircleEdge:
			return self.get_keypoints()
		elif self.type == EdgeType.PolyLineEdge:
			return self.get_keypoints()
		elif self.type == EdgeType.FilletLineEdge:
			return self.get_keypoints()

	def get_other_kp(self, kp):
		kps = self.get_end_key_points()
		if len(kps) > 1:
			if kps[0] == kp:
				return kps[1]
			else:
				return kps[0]
		else:
			return None

	def add_key_point(self, key_point):
		self._key_points.append(key_point.uid)
		key_point.add_change_handler(self.on_key_point_changed)
		key_point.add_edge(self)
		self._draw_datas = {}

	def on_key_point_changed(self, event):
		if event.type == ChangeEvent.Deleted:
			event.object.remove_edge(self)
			self.changed(ChangeEvent(self, ChangeEvent.Deleted, self))
			event.object.remove_change_handler(self.on_key_point_changed)
		self.changed(event)
		if self._type == EdgeType.ArcEdge:
			ckp = self._geometry.get_keypoint(self._key_points[0])
			if event.sender == ckp:
				self.update_linked_kps(ckp)
		if self._type == EdgeType.NurbsEdge:
			self._draw_datas = {}

	def update_linked_kps(self, ckp):
		if self._type == EdgeType.ArcEdge:
			pm = self._plane.get_global_projection_matrix()
			x = cos(self._meta_data['ea']) * self._meta_data['r']
			y = sin(self._meta_data['ea']) * self._meta_data['r']
			z = 0

			if 'end_kp' not in self._meta_data:
				self._meta_data['end_kp'] = self._geometry.create_keypoint(0,0,0).uid
			if 'start_kp' not in self._meta_data:
				self._meta_data['start_kp'] = self._geometry.create_keypoint(0,0,0).uid

			end_kp = self._geometry.get_keypoint(self._meta_data['end_kp'])
			pc1 = ckp.xyz + pm.dot(np.array([x, y, z]))
			end_kp.x = pc1[0]
			end_kp.y = pc1[1]
			end_kp.z = pc1[2]
			start_kp = self._geometry.get_keypoint(self._meta_data['start_kp'])
			x = cos(self._meta_data['sa']) * self._meta_data['r']
			y = sin(self._meta_data['sa']) * self._meta_data['r']
			z = 0
			pc1 = ckp.xyz + pm.dot(np.array([x, y, z]))
			start_kp.x = pc1[0]
			start_kp.y = pc1[1]
			start_kp.z = pc1[2]

	def distance(self, point, instance=None):
		kps = self.get_keypoints()
		if self.type == EdgeType.LineEdge:
			kp1 = kps[0]
			kp2 = kps[1]
			if kp1 is None or kp2 is None:
				return inf
			alpha1 = kp1.angle_between(kp2, point)
			alpha2 = kp2.angle_between(kp1, point)
			if alpha1 > pi:
				alpha1 = 2 * pi - alpha1
			if alpha2 > pi:
				alpha2 = 2 * pi - alpha2
			if alpha1 < pi / 2 and alpha2 < pi / 2:
				distance = kp1.distance(point) * sin(alpha1)
				return distance
			else:
				return min(kp1.distance(point), kp2.distance(point))
		elif self.type == EdgeType.NurbsEdge:
			dist = 1e12
			for i in range(1, len(kps)):
				kp1 = kps[i - 1]
				kp2 = kps[i]
				alpha1 = kp1.angle_between(kp2, point)
				alpha2 = kp2.angle_between(kp1, point)
				if alpha1 > pi:
					alpha1 = 2 * pi - alpha1
				if alpha2 > pi:
					alpha2 = 2 * pi - alpha2
				if alpha1 < pi / 2 and alpha2 < pi / 2:
					distance = kp1.distance(point) * sin(alpha1)
				else:
					distance = min(kp1.distance(point), kp2.distance(point))
				if distance < dist:
					dist = distance
			return dist
		elif self.type == EdgeType.ArcEdge:
			center = kps[0]
			radius = self.get_meta_data('r', instance) # self._meta_data['r']
			sa = self.get_meta_data('sa', instance) # self._meta_data['sa']
			ea = self.get_meta_data('ea', instance) # self._meta_data['ea']
			diff = ea - sa
			if diff < 0:
				diff += 2 * pi
			angle = center.angle2d(point)
			diff2 = angle - sa
			if diff2 < 0:
				diff2 += 2 * pi
			if diff2 > diff:
				ekp = self.get_end_key_points()
				dist = min(ekp[0].distance(point), ekp[1].distance(point))
			else:
				dist = abs(center.distance(point) - radius)
			return dist
		elif self.type == EdgeType.CircleEdge:
			center = kps[0]
			radius = self._meta_data['r']
			dist = abs(center.distance(point) - radius)
			return dist
		elif self.type == EdgeType.FilletLineEdge:
			kp = kps[0]
			radius = self._meta_data['r']
			edges = kp.get_edges()
			edges.remove(self)
			draw_data = self.get_draw_data(instance)
			if 'rect' in draw_data:
				rect = draw_data['rect']
				center = Vertex(rect[0] + rect[2] / 2, rect[1] - rect[3] / 2)
				dist = abs(center.distance(point) - radius)
				sa = draw_data['sa']
				ea = sa + draw_data['span']
				diff = ea - sa
				if diff < 0:
					diff += 2 * pi
				angle = center.angle2d(point)
				diff2 = angle - sa
				if diff2 < 0:
					diff2 += 2 * pi
				if diff2 > diff:
					dist = kp.distance(point)

			else:
				dist = kp.distance(point)
			return dist

	def get_draw_data(self, instance=None):
		edge_data = {}

		key_points = self.get_keypoints()
		if self.type == EdgeType.LineEdge:
			edges_list = key_points[0].get_edges()
			fillet1 = None
			other_edge1 = None
			for edge_item in edges_list:
				if edge_item.type == EdgeType.FilletLineEdge:
					fillet1 = edge_item
				elif edge_item is not self:
					other_edge1 = edge_item
			edges_list = key_points[1].get_edges()
			fillet2 = None
			other_edge2 = None
			for edge_item in edges_list:
				if edge_item.type == EdgeType.FilletLineEdge:
					fillet2 = edge_item
				elif edge_item is not self:
					other_edge2 = edge_item
			fillet_offset_x = 0
			fillet_offset_y = 0

			if fillet1 is not None and other_edge1 is not None:
				r = fillet1.get_meta_data("r", instance)
				a1 = self.angle(key_points[0])
				dist = get_fillet_offset_distance(key_points[0], r, self, other_edge1)
				fillet_offset_x = dist * cos(a1)
				fillet_offset_y = dist * sin(a1)

			x1 = (key_points[0].get_instance_x(instance) + fillet_offset_x )
			y1 = (key_points[0].get_instance_y(instance) + fillet_offset_y )

			fillet_offset_x = 0
			fillet_offset_y = 0

			if fillet2 is not None and other_edge2 is not None:
				r = fillet2.get_meta_data("r", instance)
				a1 = self.angle(key_points[1])
				dist = get_fillet_offset_distance(key_points[1], r, self, other_edge2)
				fillet_offset_x = dist * cos(a1)
				fillet_offset_y = dist * sin(a1)

			x2 = (key_points[1].get_instance_x(instance) + fillet_offset_x)
			y2 = (key_points[1].get_instance_y(instance) + fillet_offset_y)
			edge_data["type"] = EdgeDrawDataType.Line
			edge_data["coords"] = [Vertex(x1, y1), Vertex(x2, y2)]
		elif self.type == EdgeType.ArcEdge:
			kp = key_points[0]
			cx = kp.get_instance_x(instance)
			cy = kp.get_instance_y(instance)
			cz = kp.get_instance_z(instance)
			radius = self.get_meta_data("r", instance)
			rect = [cx - radius, cy + radius, radius * 2, radius * 2]
			start_angle = self.get_meta_data("sa", instance)
			end_angle = self.get_meta_data("ea", instance)
			span = end_angle - start_angle
			if span < 0:
				span += 2 * pi
			edge_data["type"] = EdgeDrawDataType.Arc
			edge_data["rect"] = rect
			edge_data["sa"] = start_angle
			edge_data["span"] = span
			edge_data["r"] = radius
			edge_data["c"] = Vertex(cx, cy, cz)
		elif self.type == EdgeType.FilletLineEdge:
			kp = key_points[0]
			edges_list = kp.get_edges()
			if self in edges_list:
				edges_list.remove(self)
			if len(edges_list) > 1:
				edge1 = edges_list[0]
				edge2 = edges_list[1]
				kp1 = edge1.get_other_kp(kp)
				kp2 = edge2.get_other_kp(kp)
				angle_between = kp.get_instance(instance).angle_between_untouched(kp1.get_instance(instance), kp2.get_instance(instance))
				radius = self.get_meta_data("r", instance)
				dist = radius / sin(angle_between / 2)
				angle_larger = False
				while angle_between < -2 * pi:
					angle_between += 2 * pi
				while angle_between > 2 * pi:
					angle_between -= 2 * pi
				if abs(angle_between) > pi:
					angle_larger = True
					angle = edge1.angle(kp) + angle_between / 2 + pi
				else:
					angle = edge1.angle(kp) + angle_between / 2
				if dist < 0:
					angle += pi
				cx = kp.get_instance_x(instance) + dist * cos(angle)
				cy = kp.get_instance_y(instance) + dist * sin(angle)
				rect = [cx - radius, cy + radius, radius * 2, radius * 2]

				if angle_between < 0:
					if angle_larger:
						end_angle = (edge1.angle(kp) - pi / 2)
						start_angle = (edge2.angle(kp) + pi / 2)
					else:
						end_angle = (edge1.angle(kp) + pi / 2)
						start_angle = (edge2.angle(kp) + 3 * pi / 2)
				else:
					if angle_larger:
						start_angle = (edge1.angle(kp) + pi / 2)
						end_angle = (edge2.angle(kp) - pi / 2)
					else:
						end_angle = (edge1.angle(kp) + pi / 2)
						start_angle = (edge2.angle(kp) - pi / 2)
						end_angle += pi
						start_angle += pi
				span = end_angle - start_angle
				edge_data["type"] = EdgeDrawDataType.Arc
				edge_data["rect"] = rect
				edge_data["sa"] = start_angle
				edge_data["span"] = span
				edge_data["r"] = radius
				edge_data["c"] = Vertex(cx, cy)
		elif self.type == EdgeType.CircleEdge:
			kp = key_points[0]
			cx = kp.get_instance_x(instance)
			cy = kp.get_instance_y(instance)
			radius = self.get_meta_data("r", instance)
			rect = [cx - radius, cy + radius, radius * 2, radius * 2]
			edge_data["type"] = EdgeDrawDataType.Circle
			edge_data["rect"] = rect
			edge_data["r"] = radius
			edge_data["c"] = Vertex(cx, cy)
		elif self.type == EdgeType.NurbsEdge:
			instance_name = instance
			if instance is None:
				instance_name = "None"
			if instance_name in self._draw_datas:
				edge_data = self._draw_datas[instance_name]
			else:
				kps = self.get_keypoints()
				nurbs = ns.Nurbs()
				nurbs.set_degree(self.get_meta_data("n"))
				controls = []
				coords = []
				for i in range(0, len(kps)):
					kp = kps[i]
					v = kp.get_instance_xyz(instance)
					controls.append(ns.Vertex(v[0], v[1], v[2]))
				if len(controls) > 2:
					nurbs.set_controls(controls)
					divs = len(controls) * 20
					for vert in nurbs.range(divs):
						coords.append(Vertex.from_xyz(vert.get_xyz()))
				else:
					for kp in kps:
						coords.append(Vertex.from_xyz(kp.get_instance_xyz(instance)))
				edge_data["type"] = EdgeDrawDataType.Lines
				edge_data["coords"] = coords
				self._draw_datas[instance_name] = edge_data
		return edge_data

	def angle(self, kp=None):
		kps = self.get_end_key_points()
		if self.type == EdgeType.LineEdge:
			if kp is None:
				return kps[0].angle2d(kps[1])
			else:
				if kp == kps[0]:
					return kps[0].angle2d(kps[1])
				elif kp == kps[1]:
					return kps[1].angle2d(kps[0])
				else:
					return kps[0].angle2d(kps[1])
		if self.type == EdgeType.ArcEdge:
			if kp is None:
				return self._meta_data['sa'] - pi / 2
			else:
				if kp == kps[0]:
					return self._meta_data['sa'] - pi / 2
				elif kp == kps[1]:
					return self._meta_data['ea'] - pi / 2
				else:
					center_kp = self.get_keypoints()[0]
					return center_kp.angle2d(kp) + pi / 2

	@property
	def length(self):
		if self.type == EdgeType.LineEdge:
			kps = self.get_end_key_points()
			return kps[0].distance(kps[1])
		elif self.type == EdgeType.ArcEdge:
			sa = self._meta_data['sa']
			ea = self._meta_data['ea']
			r = self._meta_data['r']
			diff = ea - sa
			if diff > 2 * pi:
				diff -= 2 * pi
			if diff < -2 * pi:
				diff += 2 * pi
			return abs(diff * r)
		elif self.type == EdgeType.FilletLineEdge:
			data = self.get_draw_data()
			span = data.get('span', 1)
			r = data.get('r', 1)
			return r*span
		elif self.type == EdgeType.CircleEdge:
			r = self._meta_data.get('r', 1)
			return 2*pi*r

	def coincident(self, key_point, coin_thress=None):
		kps = self.get_keypoints()
		if coin_thress is None:
			threshold = self._geometry.threshold
		else:
			threshold = coin_thress
		if self.type == EdgeType.LineEdge:
			kp1 = kps[0]
			kp2 = kps[1]
			alpha1 = kp1.angle_between(kp2, key_point)
			alpha2 = kp2.angle_between(kp1, key_point)
			if alpha1 > pi:
				alpha1 = 2 * pi - alpha1
			if alpha2 > pi:
				alpha2 = 2 * pi - alpha2
			if alpha1 < pi / 2 and alpha2 < pi / 2:
				distance = kp1.distance(key_point) * sin(alpha1)
				if distance < threshold:
					return True

		return False

	def split(self, key_point):
		kps = self.get_keypoints()
		second_key_point = kps[1]
		second_key_point.remove_change_handler(self.on_key_point_changed)
		second_key_point.remove_edge(self)
		self._key_points.remove(second_key_point.uid)
		self._key_points.append(key_point.uid)
		key_point.add_change_handler(self.on_key_point_changed)
		new_edge = self._geometry.create_line_edge(key_point, second_key_point)
		return new_edge

	@property
	def type(self):
		return self._type


	def initialize_change_events(self):
		self._change_events_initalized = True
		for uid in self._key_points:
			kp = self._geometry.get_keypoint(uid)
			kp.add_change_handler(self.on_key_point_changed)

	@staticmethod
	def deserialize(sketch, data):
		edge = Edge(sketch)
		if data is not None:
			edge.deserialize_data(data)
		return edge

	def serialize_json(self):
		return {
			'uid': IdObject.serialize_json(self),
			'no': NamedObservableObject.serialize_json(self),
			'type': self._type.value,
			'key_points': self._key_points,
			'meta_data': self._meta_data,
			'meta_data_parameters': self._meta_data_parameters,
			'plane': self._plane,
			'style': self._style.uid

		}

	def deserialize_data(self, data):
		IdObject.deserialize_data(self, data['uid'])
		NamedObservableObject.deserialize_data(self, data.get('no', None))
		self._type = EdgeType(data['type'])
		self._key_points = data['key_points']
		self._plane = Plane.deserialize(data.get('plane', None))
		self._style = self._geometry.document.styles.get_edge_style(data.get('style', None))
		self._geometry.document.add_late_init_object(self)
		self._meta_data = data.get('meta_data')
		self._meta_data_parameters = data.get('meta_data_parameters')
		if self._type == EdgeType.NurbsEdge:
			if "n" not in self._meta_data:
				self._meta_data["n"] = 2
		for parameter_uid in self._meta_data_parameters:
			param = self._geometry.get_parameter_by_uid(parameter_uid)
			if param is not None:
				param.add_change_handler(self.on_parameter_change)
		self._change_events_initalized = False

	def late_init(self):
		for kp_uid in self._key_points:
			kp = self._geometry.get_keypoint(kp_uid)
			if kp is not None:
				kp.add_change_handler(self.on_key_point_changed)
				kp.add_edge(self)
			else:
				self.delete()
Business/Undo.py
def on_undo(document):
	if len(document.undo_stack) > 0:
		do_object = document.undo_stack.pop()
		do_object.undo()
Business/DrawingActions.py
def create_default_header(document):
	header = document.get_drawings().create_header()
	header.name = "Default header"
	width = 0.15
	height = 0.12
	titleHeight = 0.02
	ts = 0.001
	widthRevision = 0.010
	valueTextHeight = 0.003
	textDist = valueTextHeight * 2
	rev_height = 0.01
	document.styles.get_edge_style_by_name("thin").thickness = 0.00018
	document.styles.get_edge_style_by_name("border").thickness = 0.0005

	captionTextHeight = 0.002
	pnt1 = header.create_keypoint(0, 0, 0)
	pnt2 = header.create_keypoint(width, 0, 0)
	pnt3 = header.create_keypoint(width, height + titleHeight, 0)
	pnt4 = header.create_keypoint(0, height + titleHeight, 0)
	pnt5 = header.create_keypoint(0, height / 2, 0)
	pnt6 = header.create_keypoint(width, height / 2, 0)
	pnt7 = header.create_keypoint(width / 2, height, 0)
	pnt8 = header.create_keypoint(width / 2, 0.0, 0)
	pnt9 = header.create_keypoint(width / 2, height / 4, 0)
	pnt10 = header.create_keypoint(width, height / 4, 0)
	pnt11 = header.create_keypoint(width, 3 * height / 8, 0)
	pnt12 = header.create_keypoint(width / 2, 3 * height / 8, 0)
	pnt13 = header.create_keypoint(3 * width / 4, 1 * height / 4, 0)
	pnt14 = header.create_keypoint(3 * width / 4, height / 2, 0)
	pnt15 = header.create_keypoint(widthRevision, height, 0)
	pnt16 = header.create_keypoint(widthRevision, height / 2, 0)
	pnt17 = header.create_keypoint(width, height, 0)
	pnt18 = header.create_keypoint(0, height, 0)
	pnt19 = header.create_keypoint(5 * width / 8, 1 * height / 2, 0)
	pnt20 = header.create_keypoint(5 * width / 8, height, 0)
	pnt21 = header.create_keypoint(6 * width / 8, 1 * height / 2, 0)
	pnt22 = header.create_keypoint(6 * width / 8, height, 0)
	pnt23 = header.create_keypoint(7 * width / 8, 1 * height / 2, 0)
	pnt24 = header.create_keypoint(7 * width / 8, height, 0)
	pnt25 = header.create_keypoint(width / 2 + textDist / 2, textDist * 2, 0)
	pnt26 = header.create_keypoint(4 * width / 5 + textDist / 2, textDist * 2, 0)

	pnt27 = header.create_keypoint(9 * width / 10, textDist, 0)
	pnt28 = header.create_keypoint(9 * width / 10 - textDist*1.5, textDist*4.5, 0)
	pnt29 = header.create_keypoint(9 * width / 10 + textDist*1.5, textDist*4.5, 0)

	# pnt26 = header.create_keypoint(4 * width / 5 + textDist / 2, textDist * 2, 0)
	header.create_line_edge(pnt1, pnt2).style_name = "border"
	header.create_line_edge(pnt2, pnt3).style_name = "border"
	header.create_line_edge(pnt3, pnt4).style_name = "border"
	header.create_line_edge(pnt4, pnt1).style_name = "border"
	header.create_line_edge(pnt5, pnt6)
	header.create_line_edge(pnt7, pnt8)
	header.create_line_edge(pnt9, pnt10)
	header.create_line_edge(pnt12, pnt11)
	header.create_line_edge(pnt13, pnt14)
	header.create_line_edge(pnt15, pnt16)
	header.create_line_edge(pnt17, pnt18)
	header.create_line_edge(pnt19, pnt20)
	header.create_line_edge(pnt21, pnt22)
	header.create_line_edge(pnt23, pnt24)
	header.create_line_edge(pnt25, pnt26).style_name = "thin"
	header.create_line_edge(pnt27, pnt28).style_name = "thin"
	header.create_line_edge(pnt28, pnt29).style_name = "thin"
	header.create_line_edge(pnt27, pnt29).style_name = "thin"

	for i in range(6):
		pnta = header.create_keypoint(width, height / 2 + i * rev_height, 0)
		pntb = header.create_keypoint(0, height / 2 + i * rev_height, 0)
		pntrev = header.create_keypoint(widthRevision / 2, height / 2 + rev_height / 2 + i * rev_height, 0)
		pntrtit = header.create_keypoint(widthRevision + textDist / 2, height / 2 + rev_height / 2 + i * rev_height, 0)
		pntdt = header.create_keypoint(9 * width / 16, height / 2 + rev_height / 2 + i * rev_height, 0)
		pntmk = header.create_keypoint(11 * width / 16, height / 2 + rev_height / 2 + i * rev_height, 0)
		pntchk = header.create_keypoint(13 * width / 16, height / 2 + rev_height / 2 + i * rev_height, 0)
		pntapp = header.create_keypoint(15 * width / 16, height / 2 + rev_height / 2 + i * rev_height, 0)
		if (i == 0):
			header.create_text(pntrev, "Rev", valueTextHeight).horizontal_alignment = Alignment.Center
			header.create_text(pntrtit, "Revision Title", valueTextHeight).horizontal_alignment = Alignment.Right
			header.create_text(pntdt, "Date", valueTextHeight).horizontal_alignment = Alignment.Center
			header.create_text(pntmk, "Maker", valueTextHeight).horizontal_alignment = Alignment.Center
			header.create_text(pntchk, "Checker", valueTextHeight).horizontal_alignment = Alignment.Center
			header.create_text(pntapp, "Approver", valueTextHeight).horizontal_alignment = Alignment.Center
		else:
			revtext = ""
			if (i == 1):
				revtext = "First issue"
			header.create_attribute(pntrev, "Rev" + str(i), str(i - 1), valueTextHeight).horizontal_alignment = Alignment.Center
			header.create_attribute(pntrtit, "RevText" + str(i), revtext, valueTextHeight).horizontal_alignment = Alignment.Right
			header.create_attribute(pntdt, "RevDate" + str(i), "", valueTextHeight).horizontal_alignment = Alignment.Center
			header.create_attribute(pntmk, "RevMaker" + str(i), "", valueTextHeight).horizontal_alignment = Alignment.Center
			header.create_attribute(pntchk, "RevChecker" + str(i), "", valueTextHeight).horizontal_alignment = Alignment.Center
			header.create_attribute(pntapp, "RevApprover" + str(i), "", valueTextHeight).horizontal_alignment = Alignment.Center
		header.create_line_edge(pnta, pntb)

	header.create_attribute(header.create_keypoint(textDist / 2, textDist * 1, 0), "DocTitle4", "Document Title 4",
													valueTextHeight).horizontal_alignment = Alignment.Right
	header.create_attribute(header.create_keypoint(textDist / 2, textDist * 2, 0), "DocTitle3", "Document Title 3",
													valueTextHeight).horizontal_alignment = Alignment.Right
	header.create_attribute(header.create_keypoint(textDist / 2, textDist * 3, 0), "DocTitle2", "Document Title 2",
													valueTextHeight).horizontal_alignment = Alignment.Right
	header.create_attribute(header.create_keypoint(textDist / 2, textDist * 4, 0), "DocTitle1", "Document Title 1",
													valueTextHeight).horizontal_alignment = Alignment.Right

	header.create_attribute(header.create_keypoint(textDist / 2, textDist * 7, 0), "JobTitle3", "Job Title 3",
													valueTextHeight).horizontal_alignment = Alignment.Right
	header.create_attribute(header.create_keypoint(textDist / 2, textDist * 8, 0), "JobTitle2", "Job Title 2",
													valueTextHeight).horizontal_alignment = Alignment.Right
	header.create_attribute(header.create_keypoint(textDist / 2, textDist * 9, 0), "JobTitle1", "Job Title 1",
													valueTextHeight).horizontal_alignment = Alignment.Right

	header.create_text(header.create_keypoint(width / 2 + textDist / 2, textDist * 1, 0), "Document number",
										 valueTextHeight).horizontal_alignment = Alignment.Right
	header.create_attribute(header.create_keypoint(width / 2 + textDist / 2, textDist * 3, 0), "DocNumber", "123456",
													valueTextHeight * 1.5).horizontal_alignment = Alignment.Right

	header.create_attribute(header.create_keypoint(width * 5 / 8, height * 5/16, 0), "Scale", "1 : 1",
													valueTextHeight).horizontal_alignment = Alignment.Center

	scale_text = header.create_text(header.create_keypoint(width * 1 / 2 + textDist / 2, height / 4 + textDist /2,  0), "Scale",
										 valueTextHeight/2).horizontal_alignment = Alignment.Right
	size_text = header.create_text(header.create_keypoint(width * 1 / 2 + textDist / 2, height * 3 / 8+ textDist / 2, 0), "Size",
																	valueTextHeight / 2).horizontal_alignment = Alignment.Right

	item_text = header.create_text(header.create_keypoint(width * 3 / 4 + textDist / 2, height * 3 / 8 + textDist / 2, 0), "Item",
																	valueTextHeight / 2).horizontal_alignment = Alignment.Right

	return header
Data/Part.py
class Part(Geometry):
	def __init__(self, parameters_parent, name="New part"):
	def specular(self):
	def specular(self, value):
	def color(self):
	def color(self, value):
	def update_needed(self):
	def get_keypoints(self):
	def get_keypoint(self, uid):
	def get_surfaces(self):
	def get_edges(self):
	def get_edge(self, uid):
	def get_area(self, uid):
	def get_texts(self):
	def get_new_unique_feature_name(self, name):
	def _add_feature(self, feature):
	def create_nurbs_surface(self, sketch_feature, nurbs_edges):
	def create_plane_feature(self, name, position, x_direction, y_direction):
	def add_sketch(self, sketch):
	def create_sketch_feature(self, sketch, plane_feature):
	def create_extrude_feature(self, name, sketch_feature, area, length):
	def create_revolve_feature(self, name, sketch_feature, area, length, revolve_axis):
	def create_keypoint(self, x, y, z, ts=0.000001):
	def create_arc_edge(self, center, r, sa, span, plane):
	def create_line_edge(self, key_point1, key_point2):
	def create_nurbs_edge(self, kp):
	def create_surfaces_from_revolve(self, feature):
	def create_surfaces_from_extrude(self, feature):
	def create_surface_from_nurbs_feature(self, nurbs_surface_feature):
	def update_geometry(self):
	def get_sketch(self, uid):
	def get_sketches(self):
	def get_sketch_by_name(self, name):
	def get_feature(self, uid):
	def get_feature_progression(self):
	def get_plane_features(self):
	def get_features_list(self):
	def get_sketch_features(self):
	def _cal_limits(self):
	def get_limits(self):
	def get_lines(self):
	def on_plane_feature_changed(self, event):
	def on_nurbs_surface_feature_changed(self, event):
	def on_sketch_changed(self, event):
	def on_sketch_feature_changed(self, event):
	def on_extrude_feature_changed(self, event):
	def on_revolve_feature_changed(self, event):
	def delete(self):
	def serialize_json(self):
	def deserialize(data, param_parent):
	def deserialize_data(self, data):
Data/Document.py
class Document(IdObject, Parameters):
	def __init__(self):
		IdObject.__init__(self)
		Parameters.__init__(self, "Global Parameters")
		# self._parameters = Parameters("Global Parameters")
		self._styles = Styles()
		self._geometries = Geometries(self)
		self._axes = {}
		self._margins = Margins(self)
		self._materials = Materials(self)
		self._components = Components(self)
		self._mesh = Mesh(self)
		self._sweeps = Sweeps(self)
		self._drawings = Drawings(self)
		self._analyses = Analyses(self)
		self._undo_stack = ObservableList()
		self._redo_stack = ObservableList()
		self.path = ""
		self.name = "New document.jadoc"
		self.do_update = True
		self.status_handler = None
		self.init_change_handlers()
		self._persistent_status_message = ""
		self._persistent_status_progress = 100
		self.is_modified = False
		self._version = pracedru_design_version
		self._late_initializing_object = []

	def init_change_handlers(self):
		# self.add_change_handler(self.on_parameters_changed)
		self._styles.add_change_handler(self.on_object_changed)
		self._geometries.add_change_handler(self.on_geometries_changed)
		self._materials.add_change_handler(self.on_materials_changed)
		self._components.add_change_handler(self.on_components_changed)
		self._margins.add_change_handler(self.on_margins_changed)
		self._mesh.add_change_handler(self.on_object_changed)
		self._sweeps.add_change_handler(self.on_object_changed)
		self._drawings.add_change_handler(self.on_object_changed)
		self._undo_stack.add_change_handler(self._on_undo_stack_changed)
		self._redo_stack.add_change_handler(self._on_redo_stack_changed)

	@property
	def undo_stack(self):
		return self._undo_stack

	@property
	def redo_stack(self):
		return self._redo_stack

	def add_late_init_object(self, obj):
		self._late_initializing_object.append(obj)

	def get_axes(self):
		return self._axes

	@property
	def styles(self):
		return self._styles

	def get_materials_object(self):
		return self._materials

	def get_geometries(self):
		return self._geometries

	def get_analyses(self):
		return self._analyses

	def get_materials(self):
		return self._materials

	def get_parameters(self):
		return self

	def get_components(self):
		return self._components

	def get_margins(self):
		return self._margins

	def get_mesh(self):
		return self._mesh

	def get_sweeps(self):
		return self._sweeps

	def get_drawings(self):
		return self._drawings

	def get_sketches(self):
		return self._geometries.get_sketches()

	def get_sketch_by_name(self, name):
		return self._geometries.get_sketch_by_name(name)

	def get_sketch(self, uid):
		return self._geometries.get_geometry(uid)

	def get_edge(self, uid):
		return self._geometries.get_edge(uid)

	def get_area(self, uid):
		return self._geometries.get_area(uid)

	def on_geometries_changed(self, event: ChangeEvent):
		self.changed(ChangeEvent(self, ChangeEvent.ObjectChanged, event.sender))

	def on_areas_changed(self, event):
		self.changed(event)

	def on_materials_changed(self, event):
		self.changed(event)

	def on_components_changed(self, event):
		self.changed(event)

	def on_margins_changed(self, event):
		self.changed(event)

	def on_object_changed(self, event):
		self.changed(ChangeEvent(self, ChangeEvent.ObjectChanged, event.sender))

	def set_status(self, message, progress=100, persistent=False):
		if persistent:
			self._persistent_status_message = message
			self._persistent_status_progress = progress
		if message == "":
			message = self._persistent_status_message
			progress = self._persistent_status_progress
		if self.status_handler is not None:
			self.status_handler(message, progress)

	def add_status_handler(self, status_handler):
		self.status_handler = status_handler

	def _on_undo_stack_changed(self, event):
		if event.type == ChangeEvent.ObjectAdded:
			self._redo_stack.clear()
		if event.type == ChangeEvent.ObjectRemoved:
			self._redo_stack.append(event.object)

	def _on_redo_stack_changed(self, event):
		if event.type == ChangeEvent.ObjectRemoved:
			self._undo_stack.sneak_append(event.object)
			self._undo_stack.changed(ChangeEvent(self, ChangeEvent.ValueChanged, None))

	def add_sketch(self, sketch):
		self._geometries.add_geometry(sketch)

	def serialize_json(self):
		return \
			{
				'uid': IdObject.serialize_json(self),
				'styles': self._styles,
				'params': Parameters.serialize_json(self),
				'geoms': self._geometries,
				'axes': self._axes,
				'name': self.name,
				'materials': self._materials,
				'components': self._components,
				'margins': self._margins,
				'mesh': self._mesh,
				'sweeps': self._sweeps,
				'drawings': self._drawings,
				'analysees': self._analyses,
				'path': self.path,
				'version': self._version
			}

	@staticmethod
	def deserialize(data, ignore_version=False):
		if data.get('version', [0,0,0,0]) != pracedru_design_version:
			if ignore_version:
				print("Document saved in different version of PracedruDesign.")
			else:
				raise Exception("Document saved in different version of PracedruDesign.")
		doc = Document()
		doc.deserialize_data(data)
		return doc

	def deserialize_data(self, data):
		IdObject.deserialize_data(self, data['uid'])
		Parameters.deserialize_data(self, data.get('params', None))

		self._styles = Styles.deserialize(data.get('styles', None))
		self._geometries = Geometries.deserialize(data.get('geoms', None), self)
		for axis_tuple in data.get('axes', {}).items():
			axis = Axis.deserialize(axis_tuple[1], self)
			self._axes[axis.uid] = axis
		self._materials = Materials.deserialize(data.get('materials'), self)
		self._components = Components.deserialize(data.get('components'), self)
		self._margins = Margins.deserialize(data.get('margins'), self)
		self._mesh = Mesh.deserialize(data.get('mesh'), self)
		self._sweeps = Sweeps.deserialize(data.get('sweeps'), self)
		self._drawings = Drawings.deserialize(data.get('drawings', None), self)
		self._analyses = Analyses.deserialize(data.get('analysees', None), self)
		self.name = data.get('name', "missing")
		self.path = data.get('path', "missing")
		self.init_change_handlers()
		for late_init_obj in self._late_initializing_object:
			late_init_obj.late_init()
		self._late_initializing_object = []
Data/CalcSheetAnalysis.py
class CalcSheetAnalysis(Analysis):
	def __init__(self, name, document):
		Analysis.__init__(self, name, Analysis.CalcSheetAnalysisType, document)
		self._calcs = []
		self._size = Vertex(1, 1)

	def get_calculations(self):
		return self._calcs

	def create_calculation(self):
		pass

	@property
	def size(self):
		return [self._size.x, self._size.y]
GUI/Ribbon/RibbonTextbox.py
class RibbonTextbox(QLineEdit):
	def __init__(self, default_value, change_connector, max_width=50):
		QLineEdit.__init__(self)
		self.setStyleSheet("border: 1px solid rgba(0,0,0,30%);")
		self.setText(default_value)
		self.setMaximumWidth(max_width)
		if change_connector is not None:
			self.textChanged.connect(change_connector)
GUI/Widgets/ViewWidget.py
class ViewWidget(QStackedWidget):
	def __init__(self, main_window, document):
		QStackedWidget.__init__(self, main_window)
		self._doc = document
		self._sketchView = SketchEditorViewWidget(self, document, main_window)
		self._partView = PartViewWidget(self, document)
		self._drawingView = DrawingEditorViewWidget(self, document, main_window)
		self._calcTableView = CalcTableView(self, document, main_window)
		self._calcSheetView = CalcSheetView(self, document, main_window)
		self.addWidget(self._sketchView)
		self.addWidget(self._partView)
		self.addWidget(self._drawingView)
		self.addWidget(self._calcTableView)
		self.addWidget(self._calcSheetView)
		self._current_view = None

	def on_update_view(self):
		if self._current_view is None:
			return
		self._current_view.update()

	@property
	def sketch_view(self):
		return self._sketchView

	@property
	def drawing_view(self):
		return self._drawingView

	@property
	def part_view(self):
		return self._partView

	def set_sketch_view(self, sketch):
		self.setCurrentIndex(0)
		self._sketchView.set_sketch(sketch)
		self._current_view = self._sketchView

	def set_drawing_view(self, drawing):
		self.setCurrentIndex(2)
		self._drawingView.drawing = drawing
		self._current_view = self._drawingView

	def on_zoom_fit(self):
		if self.currentIndex() == 0:
			self._sketchView.on_zoom_fit()
		elif self.currentIndex() == 1:
			self._partView.on_zoom_fit()
		elif self.currentIndex() == 2:
			self._drawingView.on_zoom_fit()

	def set_part_view(self, part):
		self.setCurrentIndex(1)
		self._partView.set_part(part)
		self._current_view = self._partView

	def set_calc_table_view(self, calc_table):
		self.setCurrentIndex(3)
		self._calcTableView.set_calc_table(calc_table)
		self._current_view = self._calcTableView

	def set_calc_sheet_view(self, calc_sheet):
		self.setCurrentIndex(4)
		self._calcSheetView.set_calc_sheet(calc_sheet)
		self._current_view = self._calcSheetView

	def on_area_selection_changed_in_table(self, selected_areas):
		self.sketch_view.selected_areas = selected_areas

	def on_kp_selection_changed_in_table(self, selected_key_points):
		self._sketchView.selected_key_points = selected_key_points

	def on_edge_selection_changed_in_table(self, selected_edges):
		self._sketchView.selected_edges = selected_edges
GUI/init.py
def gui_scale():
def is_dark_theme():
def get_stylesheet(name):
	def __init__(self):
	def make_stylesheet(self, name, path):
	def stylesheet(self, name):
def tr(string, context_name='app'):
def write_language_file():
def formula_from_locale(formula):
def formula_to_locale(formula):
class Stylesheets(object):
GUI/Widgets/NewDrawingView.py
class NewDrawingViewWidget(QDialog):
	def __init__(self, parent, document: Document):
		QDialog.__init__(self, parent)
		self._doc = document
		layout = QVBoxLayout()
		self.setLayout(layout)
		content_widget = QWidget(self)
		content_widget.setLayout(QHBoxLayout())
		layout.addWidget(content_widget)
		dialog_buttons = QDialogButtonBox(QDialogButtonBox.Cancel | QDialogButtonBox.Ok)
		layout.addWidget(dialog_buttons)
		dialog_buttons.accepted.connect(self.accept)
		dialog_buttons.rejected.connect(self.reject)

		controls_widget = QWidget()
		controls_layout = QGridLayout()
		controls_widget.setLayout(controls_layout)
		controls_layout.addWidget(QLabel("Name"), 0, 0)
		self._name_edit = QLineEdit("New Drawing")
		controls_layout.addWidget(self._name_edit, 1, 0)
		controls_layout.addWidget(QLabel("Size"), 2, 0)
		self._paper_sizes = QComboBox(controls_widget)
		sizes = []
		for size in Paper.Sizes:
			sizes.append(size)
		sizes.sort()
		self._paper_sizes.addItems(sizes)
		controls_layout.addWidget(self._paper_sizes, 3, 0)
		controls_layout.addWidget(QLabel("Header"), 4, 0)
		self._headers = QComboBox(controls_widget)

		for header in document.get_drawings().get_headers():
			self._headers.addItem(header.name)

		controls_layout.addWidget(self._headers, 5, 0)
		controls_widget.setMaximumWidth(150)

		controls_layout.addWidget(QLabel("Orientation"), 6, 0)
		self._orientations = QComboBox(controls_widget)
		self._orientations.addItems(["Landscape", "Portrait"])
		controls_layout.addWidget(self._orientations, 7, 0)
		controls_layout.addItem(QSpacerItem(0, 0, QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding), 8, 0)

		content_widget.layout().addWidget(controls_widget)
		self._header_view = SketchViewWidget(self, None, self._doc)
		content_widget.layout().addWidget(self._header_view)
		self._header_view.set_sketch(self.header)
		self._headers.currentTextChanged.connect(self.on_sketch_selection_changed)

	def on_sketch_selection_changed(self):
		sketch_name = self._sketch_combo_box.currentText()
		sketch = self._doc.get_geometries().get_sketch_by_name(sketch_name)
		self._header_view.set_sketch(sketch)

	@property
	def header(self):
		for header in self._doc.get_drawings().get_headers():
			if header.name == self._headers.currentText():
				return header
		return None

	@property
	def size(self):
		return Paper.Sizes[self._paper_sizes.currentText()]

	@property
	def name(self):
		return self._name_edit.text()

	@property
	def orientation(self):
		return self._orientations.currentIndex()
Data/CalcTableAnalysis.py
class CalcTableAnalysis(Analysis):
	def __init__(self, name, document):
		Analysis.__init__(self, name, AnalysisType.CalcTable, document)
		self._row_count = 0
		self._col_count = 0
		self._rows = {}

	def get_cell(self, row, col):
		if row in self._rows.keys():
			if col in self._rows[row].keys():
				return self._rows[row][col]
		return None

	def set_cell_value(self, row, col, value):
		cell = self.get_cell(row, col)
		if cell is None:
			if value != "":
				cell = self.create_parameter(col_index_to_letter(col) + str(row + 1))
				if row not in self._rows.keys():
					self._rows[row] = {}
				self._rows[row][col] = cell
		if value == "" and cell is not None:
			self.delete_parameter(cell.uid)
			self._rows[row].pop(col)
		elif cell is not None:
			cell.value = value

	def serialize_rows(self):
		rows = {}
		for row_tuple in self._rows.items():
			row_index = row_tuple[0]
			rows[row_index] = {}
			for col_tuple in row_tuple[1].items():
				col_index = col_tuple[0]
				parm = col_tuple[1]
				rows[row_index][col_index] = parm.uid
		return rows

	def deserialize_rows(self, data):
		for row_index in data:
			cols = data[row_index]
			for col_index in cols:
				uid = cols[col_index]
				cell = self.get_parameter_by_uid(uid)
				if row_index not in self._rows.keys():
					self._rows[int(row_index)] = {}
				self._rows[int(row_index)][int(col_index)] = cell

	def serialize_json(self):
		return {
			'uid': IdObject.serialize_json(self),
			'parameters': Parameters.serialize_json(self),
			'rows': self.serialize_rows(),
			'type': self._analysisType.value
		}

	@staticmethod
	def deserialize(data, document):
		sketch = CalcTableAnalysis("", document)
		if data is not None:
			sketch.deserialize_data(data)
		return sketch

	def deserialize_data(self, data):
		IdObject.deserialize_data(self, data['uid'])
		Parameters.deserialize_data(self, data['parameters'])
		self.deserialize_rows(data['rows'])
Data/Point3d.py
class KeyPoint(Point3d, NamedObservableObject):
	def __init__(self, parameters: Parameters, x=0.0, y=0.0, z=0.0, name="Keypoint"):
		Point3d.__init__(self, x, y, z)
		NamedObservableObject.__init__(self, name)
		self._component_parameters = [None, None, None]
		self._instances = {}
		self._parameters = parameters
		self._edges = []
		self.editable = True
		self._component_change_handlers = []
		self._component_change_handlers.append(self.on_x_param_changed)
		self._component_change_handlers.append(self.on_y_param_changed)
		self._component_change_handlers.append(self.on_z_param_changed)

	@property
	def instances(self):
		return self._instances.items()

	@property
	def parameters(self):
		return self._parameters

	def delete(self):
		self.changed(ChangeEvent(self, ChangeEvent.Deleted, self))

	def get_edges(self):
		return list(self._edges)

	def add_edge(self, edge):
		if edge not in self._edges:
			self._edges.append(edge)
			edge.add_change_handler(self.edge_changed_handler)

	def edge_changed_handler(self, event):
		if event.type == ChangeEvent.Deleted:
			self.remove_edge(event.object)

	def remove_edge(self, edge):
		if edge in self._edges:
			edge.remove_change_handler(self.edge_changed_handler)
			self._edges.remove(edge)

	def get_x_parameter(self):
		return self._component_parameters[0]

	def get_y_parameter(self):
		return self._component_parameters[1]

	def get_z_parameter(self):
		return self._component_parameters[2]

	def create_value_change_object(self, new_value, old_value, component, instance_uid):
		change_object = {
			'new value': new_value,
			'old value': old_value,
			'name': components[component],
			'component': component,
			'instance': instance_uid
		}
		return change_object

	def set_instance_generic(self, instance_uid, value, component):
		changed = False
		old_value = self.get_instance_generic(instance_uid, component)
		if instance_uid is None:
			self.xyz[component] = value
			changed = True
		else:
			if instance_uid in self._instances:
				instance_vertice = self._instances[instance_uid]
				instance_vertice.xyz[component] = value
				if self.equals(instance_vertice):
					self._instances.pop(instance_uid)
				changed = True
			else:
				if value != self.xyz[component]:
					instance_vertice = Vertex(self.x, self.y, self.z)
					instance_vertice.xyz[component] = value
					self._instances[instance_uid] = instance_vertice
					changed = True
		if changed:
			name = components[component]
			change_object = self.create_value_change_object(value, old_value, component, instance_uid)
			self.changed(ChangeEvent(self, ChangeEvent.ValueChanged, change_object))

	def set_instance_x(self, instance_uid, value):
		self.set_instance_generic(instance_uid, value, 0)

	def set_instance_y(self, instance_uid, value):
		self.set_instance_generic(instance_uid, value, 1)

	def set_instance_z(self, instance_uid, value):
		self.set_instance_generic(instance_uid, value, 2)

	def get_instance_generic(self, instance_uid, component):
		if self._component_parameters[component] is None:
			if instance_uid in self._instances:
				return self._instances[instance_uid].xyz[component]
			return self.xyz[component]
		else:
			return self._component_parameters[component].get_instance_value(instance_uid)

	def get_instance_x(self, instance_uid):
		return self.get_instance_generic(instance_uid, 0)

	def get_instance_y(self, instance_uid):
		return self.get_instance_generic(instance_uid, 1)

	def get_instance_z(self, instance_uid):
		return self.get_instance_generic(instance_uid, 2)

	def get_instance_xyz(self, instance_uid):
		if instance_uid in self._instances:
			return self._instances[instance_uid].xyz
		return self.xyz

	def get_instance(self, instance_uid):
		if instance_uid in self._instances:
			return self._instances[instance_uid]
		return self

	@property
	def x(self):
		return self.xyz[0]

	@property
	def y(self):
		return self.xyz[1]

	@property
	def z(self):
		return self.xyz[2]

	@x.setter
	def x(self, value):
		self.set_instance_generic(None, value, 0)

	@y.setter
	def y(self, value):
		self.set_instance_generic(None, value, 1)

	@z.setter
	def z(self, value):
		self.set_instance_generic(None, value, 2)

	def set_parameter_generic(self, param_uid, component):
		old_value = self.get_instance_generic(None, component)
		if self._component_parameters[component] is not None:
			self._component_parameters[component].remove_change_handler(self._component_change_handlers[component])
			self._component_parameters[component] = None
		if param_uid is not None:
			param =  self._parameters.get_parameter_by_uid(param_uid)
			self._component_parameters[component] = param
			if param is not None:
				new_value = self.get_instance_generic(None, component)
				param.add_change_handler(self._component_change_handlers[component])
				change_object = self.create_value_change_object(new_value, old_value, component, None)
				self._component_change_handlers[component](ChangeEvent(self, ChangeEvent.ValueChanged, change_object))

	def set_x_parameter(self, param_uid):
		self.set_parameter_generic(param_uid, 0)

	def set_y_parameter(self, param_uid):
		self.set_parameter_generic(param_uid, 1)

	def set_z_parameter(self, param_uid):
		self.set_parameter_generic(param_uid, 2)

	def on_param_changed_generic(self, event, component):
		instance = None
		if 'instance' in event.object:
			instance = event.object['instance']
		old_value = self.get_instance_generic(instance, component)
		new_value = float(self._component_parameters[component].evaluate(instance))
		self.set_instance_generic(instance, new_value, component)
		if event.type == ChangeEvent.Deleted:
			event.object.remove_change_handler(self._component_change_handlers[component])
			self._component_parameters[component] = None
		change_object = self.create_value_change_object(new_value, old_value, component, instance)
		self.changed(ChangeEvent(self, ChangeEvent.ValueChanged, change_object))

	def on_x_param_changed(self, event):
		self.on_param_changed_generic(event, 0)

	def on_y_param_changed(self, event):
		self.on_param_changed_generic(event, 1)

	def on_z_param_changed(self, event):
		self.on_param_changed_generic(event, 2)

	def get_param_uid(self, param):
		if param is None:
			return None
		else:
			return param.uid

	def serialize_json(self):
		return \
			{
				'p3d': Point3d.serialize_json(self),
				'no': NamedObservableObject.serialize_json(self),
				'instances': self._instances,
				'x_param_uid': self.get_param_uid(self._component_parameters[0]),
				'y_param_uid': self.get_param_uid(self._component_parameters[1]),
				'z_param_uid': self.get_param_uid(self._component_parameters[2])
			}

	@staticmethod
	def deserialize(data, parameters):
		key_point = KeyPoint(parameters)
		key_point.deserialize_data(data)
		return key_point

	def deserialize_data(self, data):
		Point3d.deserialize_data(self, data.get('p3d'))
		if 'no' in data:
			NamedObservableObject.deserialize_data(self, data['no'])
		else:
			self.name = "Keypoint"
		for instance_tuple in data.get('instances', {}).items():
			instance_uid = instance_tuple[0]
			vertex = Vertex.deserialize(instance_tuple[1])
			self._instances[instance_uid] = vertex
		self.set_parameter_generic(data['x_param_uid'], 0)
		self.set_parameter_generic(data['y_param_uid'], 1)
		self.set_parameter_generic(data['z_param_uid'], 2)


Question: from PyQt5.QtCore import QFileInfo
from PyQt5.QtCore import QPoint
from PyQt5.QtCore import QSettings
from PyQt5.QtCore import QSize
from PyQt5.QtCore import QStandardPaths
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QKeySequence
from PyQt5.QtWidgets import QAction
from PyQt5.QtWidgets import QDialog
from PyQt5.QtWidgets import QDockWidget
from PyQt5.QtWidgets import QFileDialog
from PyQt5.QtWidgets import QMainWindow
from PyQt5.QtWidgets import QMessageBox
from PyQt5.QtWidgets import QProgressBar
from PyQt5.QtWidgets import QToolBar
from Business.DrawingActions import create_default_header
from Business.SketchActions import create_add_sketch_to_parent
from Business.Undo import on_undo, on_redo
from Data.Areas import Area
from Data.CalcSheetAnalysis import CalcSheetAnalysis
from Data.CalcTableAnalysis import CalcTableAnalysis
from Data.Document import Document
from Data.Drawings import Drawing
from Data.Edges import Edge
from Data.Feature import FeatureType
from Data.Parameters import Parameters, ParametersBase
from Data.Part import Part, Feature
from Data.Point3d import KeyPoint
from Data.Sketch import Sketch
from GUI import *
from GUI.ActionsStates import ActionStates
from GUI.Icons import get_icon
from GUI.Ribbon.RibbonButton import RibbonButton
from GUI.Ribbon.RibbonTextbox import RibbonTextbox
from GUI.Ribbon.RibbonWidget import RibbonWidget
from GUI.Widgets.GeometryView import GeometryDock
from GUI.Widgets.NewDrawingView import NewDrawingViewWidget
from GUI.Widgets.ParametersWidget import ParametersWidget
from GUI.Widgets.PropertiesView import PropertiesDock
from GUI.Widgets.TreeView import TreeViewDock
from GUI.Widgets.ViewWidget import ViewWidget
from GUI.init import tr, is_dark_theme, get_stylesheet, plugin_initializers, write_language_file
import Business
import GUI.Plugins
			try:
				doc = Business.load_document(file_path, True)
			except ValueError as e:
				QMessageBox.information(self, "Error on load", "ValueError" + str(e))
				return
			except KeyError as e:
				QMessageBox.information(self, "Error on load", "Key Error" + str(e))
				return
			main_window = MainWindow(doc)
			main_window.show()

			if (self._document.is_modified is False) and self._document.path == "":
				self.close()
		return

	# def on_insert_sketch(self):
	# self._viewWidget.on_insert_sketch()

	def on_insert_part_in_drawing(self):
		self._viewWidget.drawing_view.on_insert_part()

	def on_insert_dim_ann_in_drawing(self):
		pass

	def on_revolve_area(self):
		self._viewWidget.part_view.on_revolve_area()

	def on_extrude_area(self):
		self._viewWidget.part_view.on_insert_extrude()

	def on_create_nurbs_surface(self):
		self._viewWidget.part_view.on_create_nurbs_surface()

	def on_show_surfs(self):
		self._viewWidget.part_view.show_surfaces = self._show_surfs_action.isChecked()

	def on_show_lines(self):
		self._viewWidget.part_view.show_lines = self._show_lines_action.isChecked()

	def on_show_planes(self):
		self._viewWidget.part_view.show_planes = self._show_planes_action.isChecked()

	def on_add_field(self):
		self._viewWidget.drawing_view.on_add_field()

	def on_add_sqr_formula(self):
		pass

	def on_about(self):
		QMessageBox.about(self, "Pracedru Design", "Conceptualized, designed and programmed by Magnus Jørgensen.\nCopyright © Magnus Jørgensen.\nThis program is licensed with BSD 3-claus license.")

	def on_undo(self):
		on_undo(self._document)

	def on_redo(self):
		on_redo(self._document)

	def on_write_lang(self):
		write_language_file()

	def on_save(self):
		if self._document.path == "" or self._document.name == "":
			return self.on_save_as()
		else:
			Business.save_document(self._document)
			return True

	def on_save_as(self):
		docs_location = QStandardPaths.standardLocations(QStandardPaths.DocumentsLocation)
		default_path = docs_location[0]
		file_types = "Text File(*.jadoc)"
		file_name = QFileDialog.getSaveFileName(self, "Save file", default_path, file_types)
		if file_name[0] != "":
			if ".jadoc" in file_name[0]:
				file_info = QFileInfo(file_name[0])
			else:
				file_info = QFileInfo(file_name[0] + ".jadoc")
			self._document.path = file_info.absolutePath()
			self._document.name = file_info.fileName()
			self.setWindowTitle("{s[0]} - {s[1]}".format(s=[self._document.name, self._Title]))
			self.on_save()
			return True
		return False

	def on_ribbon_changed(self):
		pass

	def on_add_sketch_to_document(self):
		create_add_sketch_to_parent(self._document)

	def on_tree_selection_changed(self, selection):
		if len(selection) == 1:
			if type(selection[0]) is Sketch:
				self._viewWidget.set_sketch_view(selection[0])
				self._geometry_dock.set_sketch(selection[0])
				self._ribbon_widget.setCurrentIndex(1)
			if type(selection[0]) is KeyPoint:
				self._viewWidget.sketch_view.selected_key_points = [selection[0]]
			if type(selection[0]) is Edge:
				self._viewWidget.sketch_view.selected_edges = [selection[0]]
			if issubclass(type(selection[0]), Area):
				self._viewWidget.sketch_view.selected_areas = [selection[0]]
			if type(selection[0]) is Drawing:
				self._viewWidget.set_drawing_view(selection[0])
				self._ribbon_widget.setCurrentIndex(4)
			if issubclass(type(selection[0]), ParametersBase):
				self.parameters_widget.set_parameters(selection[0])
			if type(selection[0]) is KeyPoint:
				self._viewWidget.on_kp_selection_changed_in_table(selection)
			if type(selection[0]) is Part:
				self._viewWidget.set_part_view(selection[0])
				self._ribbon_widget.setCurrentIndex(2)
			if type(selection[0]) is Feature:
				if selection[0].feature_type == FeatureType.SketchFeature:
					sketch = selection[0].get_objects()[0]
					self._viewWidget.set_sketch_view(sketch)
					self._geometry_dock.set_sketch(sketch)
					self._ribbon_widget.setCurrentIndex(1)
					self.parameters_widget.set_parameters(sketch)
			self._properties_dock.set_item(selection[0])

Answer: 			if type(selection[0]) is CalcTableAnalysis: