diff --git a/BuildingPy.py b/BuildingPy.py index 66046b2..e87a59d 100644 --- a/BuildingPy.py +++ b/BuildingPy.py @@ -20,20 +20,25 @@ import json from packages.svg.path import parse_path import importlib +from typing import Union +import packages.helper as helper +from abstract.boundingbox import * +from objects.steelshape import * +import requests import sys, os, math, json from collections import defaultdict import sys, os, math -from typing import Union -from objects import profile import urllib.request -import re import string, random, json import urllib import xml.etree.ElementTree as ET -from packages.svg.path import path +import re +from . import path from math import sqrt, cos, sin, acos, degrees, radians, log, pi from bisect import bisect from abc import ABC, abstractmethod +from objects import profile +from packages.svg.path import path class Serializable: @@ -831,9 +836,9 @@ def __init__(self, origin: Point, x_axis, y_axis, z_axis) -> 'CoordinateSystem': from abstract.vector import Vector self.id = generateID() self.Origin = origin - self.X_axis = Vector.normalize(x_axis) - self.Y_axis = Vector.normalize(y_axis) - self.Z_axis = Vector.normalize(z_axis) + self.X_axis = x_axis + self.Y_axis = y_axis + self.Z_axis = z_axis @classmethod def by_origin(coordinate_system, origin: Point) -> 'CoordinateSystem': @@ -2001,8 +2006,11 @@ def by_point_main_vector(NewOriginCoordinateSystem: Point, DirectionVectorZ: Vec def __str__(self): return f"{__class__.__name__}(Origin = " + f"{self.Origin}, X_axis = {self.Xaxis}, Y_Axis = {self.Y_axis}, Z_Axis = {self.Z_axis})" +X_Axis = Vector(1,0,0) +Y_Axis = Vector(0,1,0) +Z_Axis = Vector(0,0,1) -CSGlobal = CoordinateSystem(Point(0, 0, 0), X_axis, Y_Axis, Z_Axis) +CSGlobal = CoordinateSystem(Point(0, 0, 0), X_Axis, Y_Axis, Z_Axis) class BuildingPy(Serializable): def __init__(self, name=None, number=None): self.name: str = name @@ -3778,7 +3786,27 @@ def __init__(self, *args): #self.reference = None #self.visibility = None super().__init__(to_array(*args)) + + @property + def area(self) -> 'float': + """Calculates the area of the 2d PolyCurve. + + Returns: + float: The area of the 2d poly curve. + we are assuming the PolyCurve is wound counter-clockwise. + """ + for line in self: + #if isinstance(line, Arc): + # + #else: + #check direction of line + #start - end, for counterclockwiseness + #when start.x < end.x, this is a bottom line. we'll substract this from the area. + dx = line.start.x - line.end.x + averagey = (line.start.y + line.end.y) / 2 + area = dx * averagey + @property def length(self) -> 'float': """Calculates the total length of the PolyCurve. @@ -6620,760 +6648,690 @@ def by_baseline_height(self, baseline: Line, height: float, thickness: float, na for j in range(int(len(p1.extrusion.verts) / 3)): p1.colorlst.append(colorrgbint) return p1 -jsonFile = "https://raw.githubusercontent.com/3BMLabs/Project-Ocondat/master/steelprofile.json" -url = urllib.request.urlopen(jsonFile) -data = json.loads(url.read()) - - -def is_rectangle_format(shape_name): - match = re.match(r'^(\d{1,4})x(\d{1,4})$', shape_name) - if match: - width, height = int(match.group(1)), int(match.group(2)) - if 0 <= width <= 10000 and 0 <= height <= 10000: - return True, width, height - return False, 0, 0 - -class _getProfileDataFromDatabase: - def __init__(self, name): - self.name = name - self.shape_coords = None - self.shape_name = None - self.synonyms = None - for item in data: - for i in item.values(): - synonymList = i[0]["synonyms"] - if self.name.lower() in [synonym.lower() for synonym in synonymList]: - self.shape_coords = i[0]["shape_coords"] - self.shape_name = i[0]["shape_name"] - self.synonyms = i[0]["synonyms"] - if self.shape_coords == None: - check_rect, width, height = is_rectangle_format(name) - if check_rect: - self.shape_coords = [width, height] - self.shape_name = "Rectangle" - self.synonyms = name +sqrt2 = math.sqrt(2) -class nameToProfile: - def __init__(self, name1, segmented = True): - profile_data = _getProfileDataFromDatabase(name1) - if profile_data == None: - print(f"profile {name1} not recognised") - profile_name = profile_data.shape_name - if profile_name == None: - profile_data = _getProfileDataFromDatabase(project.structural_fallback_element) - print(f"Error, profile '{name1}' not recognised, define in {jsonFile} | fallback: '{project.structural_fallback_element}'") - profile_name = profile_data.shape_name - self.profile_data = profile_data - self.profile_name = profile_name - name = profile_data.name - self.d1 = profile_data.shape_coords - d1 = self.d1 - if profile_name == "C-channel parallel flange": - prof = CChannelParallelFlange(name,d1[0],d1[1],d1[2],d1[3],d1[4],d1[5]) - elif profile_name == "C-channel sloped flange": - prof = CChannelSlopedFlange(name,d1[0],d1[1],d1[2],d1[3],d1[4],d1[5],d1[6],d1[7],d1[8]) - elif profile_name == "I-shape parallel flange": - prof = IShapeParallelFlange(name,d1[0],d1[1],d1[2],d1[3],d1[4]) - elif profile_name == "I-shape sloped flange": - prof = IShapeParallelFlange(name, d1[0], d1[1], d1[2], d1[3], d1[4]) - #Todo: add sloped flange shape - elif profile_name == "Rectangle": - prof = Rectangle(name,d1[0], d1[1]) - elif profile_name == "Round": - prof = Round(name, d1[1]) - elif profile_name == "Round tube profile": - prof = Roundtube(name, d1[0], d1[1]) - elif profile_name == "LAngle": - prof = LAngle(name,d1[0],d1[1],d1[2],d1[3],d1[4],d1[5],d1[6],d1[7]) - elif profile_name == "TProfile": - prof = TProfileRounded(name, d1[0], d1[1], d1[2], d1[3], d1[4], d1[5], d1[6], d1[7], d1[8]) - elif profile_name == "Rectangle Hollow Section": - prof = RectangleHollowSection(name,d1[0],d1[1],d1[2],d1[3],d1[4]) - self.profile = prof - self.data = d1 - pc2d = self.profile.curve # 2D polycurve - if segmented == True: - pc3d = PolyCurve.by_polycurve_2D(pc2d) - pcsegment = PolyCurve.segment(pc3d, 10) - pc2d2 = pcsegment.to_polycurve_2D() - else: - pc2d2 = pc2d - self.polycurve2d = pc2d2 +# Hierachie: +# point 2D +# line 2D +# PolyCurve2D 2D +# profile is een parametrische vorm heeft als resultaat een 2D curve +# section is een profiel met eigenschappen HEA200, 200,200,10,10,5 en eventuele rekenkundige eigenschappen. +# beam is een object wat in 3D zit met materiaal enz. -def justifictionToVector(plycrv2D: PolyCurve2D, XJustifiction, Yjustification, ey=None, ez=None): - - # print(XJustifiction) - xval = [] - yval = [] - for i in plycrv2D.curves: - xval.append(i.start.x) - yval.append(i.start.y) +class Profile(Serializable): - #Rect - xmin = min(xval) - xmax = max(xval) - ymin = min(yval) - ymax = max(yval) + def __init__(self, name: str, description: str, IFC_profile_def: str, height: float, width: float, + tw: float = None, tf: float = None): + """Creates a profile profile. - b = xmax-xmin - h = ymax-ymin + Args: + name (str): _description_ + description (str): _description_ + IFC_profile_def (str): _description_ + height (_type_): _description_ + width (_type_): _description_ + """ - # print(b, h) + self.IFC_profile_def = IFC_profile_def + self.ID = generateID() + self.name = name + self.description = description + self.curve = [] + self.height = height + self.width = width + self.tw = tw + self.tf = tf + self.type = None - dxleft = -xmax - dxright = -xmin - dxcenter = dxleft - 0.5 * b #CHECK - dxorigin = 0 + def __str__(self): + return f"{self.type} ({self.name})" - dytop = -ymax - dybottom = -ymin - dycenter = dytop - 0.5 * h #CHECK - dyorigin = 0 +class CChannelParallelFlange(Profile): + def __init__(self, name, height, width, tw, tf, r, ex): + super().__init__(name, "C-channel with parallel flange", "IfcUShapeProfileDef", height, width, tw, tf) - if XJustifiction == "center": - dx = dxorigin #TODO - elif XJustifiction == "left": - dx = dxleft - elif XJustifiction == "right": - dx = dxright - elif XJustifiction == "origin": - dx = dxorigin #TODO - else: - dx = 0 + # parameters + self.type = __class__.__name__ - if Yjustification == "center": - dy = dyorigin #TODO - elif Yjustification == "top": - dy = dytop - elif Yjustification == "bottom": - dy = dybottom - elif Yjustification == "origin": - dy = dyorigin #TODO - else: - dy = 0 + self.r1 = r # web fillet + self.ex = ex # centroid horizontal - # print(dx, dy) - v1 = Vector2(dx, dy) - # v1 = Vector2(0, 0) + # describe points + p1 = Point(-ex, -height / 2) # left bottom + p2 = Point(width - ex, -height / 2) # right bottom + p3 = Point(width - ex, -height / 2 + tf) + p4 = Point(-ex + tw + r, -height / 2 + tf) # start arc + p5 = Point(-ex + tw + r, -height / 2 + tf + r) # second point arc + p6 = Point(-ex + tw, -height / 2 + tf + r) # end arc + p7 = Point(-ex + tw, height / 2 - tf - r) # start arc + p8 = Point(-ex + tw + r, height / 2 - tf - r) # second point arc + p9 = Point(-ex + tw + r, height / 2 - tf) # end arc + p10 = Point(width - ex, height / 2 - tf) + p11 = Point(width - ex, height / 2) # right top + p12 = Point(-ex, height / 2) # left top - return v1 -def rgb_to_int(rgb): - r, g, b = [max(0, min(255, c)) for c in rgb] + # describe curves + l1 = Line(p1, p2) + l2 = Line(p2, p3) + l3 = Line(p3, p4) + l4 = Arc2D(p4, p5, p6) + l5 = Line(p6, p7) + l6 = Arc2D(p7, p8, p9) + l7 = Line(p9, p10) + l8 = Line(p10, p11) + l9 = Line(p11, p12) + l10 = Line(p12, p1) - return (255 << 24) | (r << 16) | (g << 8) | b + self.curve = PolyCurve.by_joined_curves( + [l1, l2, l3, l4, l5, l6, l7, l8, l9, l10]) -class Material: - def __init__(self): - self.name = "none" - self.color = None - self.colorint = None +class CChannelSlopedFlange(Profile): + def __init__(self, name, height, width, tw, tf, r1, r2, tl, sa, ex): + super().__init__(name, "C-channel with sloped flange", "IfcUShapeProfileDef", height, width, tw, tf) - @classmethod - def byNameColor(cls, name, color): - M1 = Material() - M1.name = name - M1.color = color - M1.colorint = rgb_to_int(color) - return M1 + self.r1 = r1 # web fillet + r11 = r1 / sqrt2 + self.r2 = r2 # flange fillet + r21 = r2 / sqrt2 + self.tl = tl # flange thickness location from right + self.sa = math.radians(sa) # the angle of sloped flange in degrees + self.ex = ex # centroid horizontal + # describe points + p1 = Point2D(-ex, -height / 2) # left bottom + p2 = Point2D(width - ex, -height / 2) # right bottom + p3 = Point2D(width - ex, -height / 2 + tf - math.tan(self.sa) + * tl - r2) # start arc + p4 = Point2D(width - ex - r2 + r21, -height / 2 + tf - + math.tan(self.sa) * tl - r2 + r21) # second point arc + p5 = Point2D(width - ex - r2 + math.sin(self.sa) * r2, -height / + 2 + tf - math.tan(self.sa) * (tl - r2)) # end arc + p6 = Point2D(-ex + tw + r1 - math.sin(self.sa) * r1, -height / 2 + + tf + math.tan(self.sa) * (width - tl - tw - r1)) # start arc + p7 = Point2D(-ex + tw + r1 - r11, -height / 2 + tf + math.tan(self.sa) + * (width - tl - tw - r1) + r1 - r11) # second point arc + p8 = Point2D(-ex + tw, -height / 2 + tf + math.tan(self.sa) + * (width - tl - tw) + r1) # end arc + p9 = Point2D(p8.x, -p8.y) # start arc + p10 = Point2D(p7.x, -p7.y) # second point arc + p11 = Point2D(p6.x, -p6.y) # end arc + p12 = Point2D(p5.x, -p5.y) # start arc + p13 = Point2D(p4.x, -p4.y) # second point arc + p14 = Point2D(p3.x, -p3.y) # end arc + p15 = Point2D(p2.x, -p2.y) # right top + p16 = Point2D(p1.x, -p1.y) # left top -#Building Materials -BaseConcrete = Material.byNameColor("Concrete", Color().RGB([192, 192, 192])) -BaseTimber = Material.byNameColor("Timber", Color().RGB([191, 159, 116])) -BaseSteel = Material.byNameColor("Steel", Color().RGB([237, 28, 36])) -BaseOther = Material.byNameColor("Other", Color().RGB([150, 150, 150])) -BaseBrick = Material.byNameColor("Brick", Color().RGB([170, 77, 47])) -BaseBrickYellow = Material.byNameColor("BrickYellow", Color().RGB([208, 187, 147])) - -#GIS Materials -BaseBuilding = Material.byNameColor("Building", Color().RGB([150, 28, 36])) -BaseWater = Material.byNameColor("Water", Color().RGB([139, 197, 214])) -BaseGreen = Material.byNameColor("Green", Color().RGB([175, 193, 138])) -BaseInfra = Material.byNameColor("Infra", Color().RGB([234, 234, 234])) -BaseRoads = Material.byNameColor("Infra", Color().RGB([140, 140, 140])) + # describe curves + l1 = Line2D(p1, p2) + l2 = Line2D(p2, p3) + l3 = Arc2D(p3, p4, p5) + l4 = Line2D(p5, p6) + l5 = Arc2D(p6, p7, p8) + l6 = Line2D(p8, p9) + l7 = Arc2D(p9, p10, p11) + l8 = Line2D(p11, p12) + l9 = Arc2D(p12, p13, p14) + l10 = Line2D(p14, p15) + l11 = Line2D(p15, p16) + l12 = Line2D(p16, p1) -#class Materialfinish + self.curve = PolyCurve2D().by_joined_curves( + [l1, l2, l3, l4, l5, l6, l7, l8, l9, l10, l11, l12]) -class Node: - """The `Node` class represents a geometric or structural node within a system, defined by a point in space, along with optional attributes like a direction vector, identifying number, and other characteristics.""" - def __init__(self, point=None, vector=None, number=None, distance=0.0, diameter=None, comments=None): - """"Initializes a new Node instance. - - - `id` (str): A unique identifier for the node. - - `type` (str): The class name, "Node". - - `point` (Point, optional): The location of the node in 3D space. - - `vector` (Vector, optional): A vector indicating the orientation or direction associated with the node. - - `number` (any, optional): An identifying number or label for the node. - - `distance` (float): A scalar attribute, potentially representing distance from a reference point or another node. - - `diameter` (any, optional): A diameter associated with the node, useful in structural applications. - - `comments` (str, optional): Additional comments or notes about the node. - """ - self.id = generateID() - self.point = point if isinstance(point, Point) else None - self.vector = vector if isinstance(vector, Vector) else None - self.number = number - self.distance = distance - self.diameter = diameter - self.comments = comments +class IShapeParallelFlange(Profile): + def __init__(self, name, height, width, tw, tf, r): + super().__init__(name, "I Shape profile with parallel flange", "IfcUShapeProfileDef", height, width, tw, + tf) - def serialize(self) -> dict: - """Serializes the node's attributes into a dictionary. - This method allows for the node's properties to be easily stored or transmitted in a dictionary format. + self.r = r # web fillet + self.r1 = r1 = r / sqrt2 - #### Returns: - `dict`: A dictionary containing the serialized attributes of the node. - """ - id_value = str(self.id) if not isinstance( - self.id, (str, int, float)) else self.id - return { - 'id': id_value, - 'type': self.type, - 'point': self.point.serialize() if self.point else None, - 'vector': self.vector.serialize() if self.vector else None, - 'number': self.number, - 'distance': self.distance, - 'diameter': self.diameter, - 'comments': self.comments - } + # describe points + p1 = Point2D(width / 2, -height / 2) # right bottom + p2 = Point2D(width / 2, -height / 2 + tf) + p3 = Point2D(tw / 2 + r, -height / 2 + tf) # start arc + # second point arc + p4 = Point2D(tw / 2 + r - r1, (-height / 2 + tf + r - r1)) + p5 = Point2D(tw / 2, -height / 2 + tf + r) # end arc + p6 = Point2D(tw / 2, height / 2 - tf - r) # start arc + p7 = Point2D(tw / 2 + r - r1, height / 2 - tf - r + r1) # second point arc + p8 = Point2D(tw / 2 + r, height / 2 - tf) # end arc + p9 = Point2D(width / 2, height / 2 - tf) + p10 = Point2D((width / 2), (height / 2)) # right top + p11 = Point2D(-p10.x, p10.y) # left top + p12 = Point2D(-p9.x, p9.y) + p13 = Point2D(-p8.x, p8.y) # start arc + p14 = Point2D(-p7.x, p7.y) # second point arc + p15 = Point2D(-p6.x, p6.y) # end arc + p16 = Point2D(-p5.x, p5.y) # start arc + p17 = Point2D(-p4.x, p4.y) # second point arc + p18 = Point2D(-p3.x, p3.y) # end arc + p19 = Point2D(-p2.x, p2.y) + p20 = Point2D(-p1.x, p1.y) - @staticmethod - def deserialize(data: dict) -> 'Node': - """Recreates a Node object from a dictionary of serialized data. + # describe curves + l1 = Line2D(p1, p2) + l2 = Line2D(p2, p3) + l3 = Arc2D(p3, p4, p5) + l4 = Line2D(p5, p6) + l5 = Arc2D(p6, p7, p8) + l6 = Line2D(p8, p9) + l7 = Line2D(p9, p10) + l8 = Line2D(p10, p11) + l9 = Line2D(p11, p12) + l10 = Line2D(p12, p13) + l11 = Arc2D(p13, p14, p15) + l12 = Line2D(p15, p16) + l13 = Arc2D(p16, p17, p18) + l14 = Line2D(p18, p19) + l15 = Line2D(p19, p20) + l16 = Line2D(p20, p1) - #### Parameters: - - data (dict): The dictionary containing the node's serialized data. + self.curve = PolyCurve2D().by_joined_curves( + [l1, l2, l3, l4, l5, l6, l7, l8, l9, l10, l11, l12, l13, l14, l15, l16]) - #### Returns: - `Node`: A new Node object initialized with the data from the dictionary. - """ - node = Node() - node.id = data.get('id') - node.type = data.get('type') - node.point = Point.deserialize( - data['point']) if data.get('point') else None - node.vector = Vector.deserialize( - data['vector']) if data.get('vector') else None - node.number = data.get('number') - node.distance = data.get('distance') - node.diameter = data.get('diameter') - node.comments = data.get('comments') +class Rectangle(Profile): + def __init__(self, name, width, height): + super().__init__(name, "Rectangle", "IfcRectangleProfileDef", height, width) - return node - # merge - def merge(self): - """Merges this node with others in a project according to defined rules. + # describe points + p1 = Point2D(width / 2, -height / 2) # right bottom + p2 = Point2D(width / 2, height / 2) # right top + p3 = Point2D(-width / 2, height / 2) # left top + p4 = Point2D(-width / 2, -height / 2) # left bottom - The actual implementation of this method should consider merging nodes based on proximity or other criteria within the project context. - """ - if project.node_merge == True: - pass - else: - pass + # describe curves + l1 = Line2D(p1, p2) + l2 = Line2D(p2, p3) + l3 = Line2D(p3, p4) + l4 = Line2D(p4, p1) - # snap - def snap(self): - """Adjusts the node's position based on snapping criteria. + self.curve = PolyCurve2D().by_joined_curves([l1, l2, l3, l4]) - This could involve aligning the node to a grid, other nodes, or specific geometric entities. - """ - pass +class Round(Profile): + def __init__(self, name, r): + super().__init__(name, "Round", "IfcCircleProfileDef", r*2, r*2) - def __str__(self) -> str: - """Generates a string representation of the Node. + self.r = r - #### Returns: - `str`: A string that represents the Node, including its type and potentially other identifying information. - """ + dr = r / sqrt2 # grootste deel - return f"{self.type}" + # describe points + p1 = Point2D(r, 0) # right middle + p2 = Point2D(dr, dr) + p3 = Point2D(0, r) # middle top + p4 = Point2D(-dr, dr) + p5 = Point2D(-r, 0) # left middle + p6 = Point2D(-dr, -dr) + p7 = Point2D(0, -r) # middle bottom + p8 = Point2D(dr, -dr) -def colorlist(extrus, color): - colorlst = [] - for j in range(int(len(extrus.verts) / 3)): - colorlst.append(color) - return (colorlst) + # describe curves + l1 = Arc2D(p1, p2, p3) + l2 = Arc2D(p3, p4, p5) + l3 = Arc2D(p5, p6, p7) + l4 = Arc2D(p7, p8, p1) + self.curve = PolyCurve2D().by_joined_curves([l1, l2, l3, l4]) -# ToDo Na update van color moet ook de colorlist geupdate worden -class Frame(Serializable): - def __init__(self): - self.id = generateID() - self.name = "None" - self.profileName = "None" - self.extrusion = None - self.comments = None - self.structuralType = None - self.start = None - self.end = None - self.curve = None # 2D polycurve of the sectionprofile - self.curve3d = None # Translated 3D polycurve of the sectionprofile - self.length = 0 - self.points = [] - self.coordinateSystem: CoordinateSystem = CSGlobal - self.YJustification = "Origin" # Top, Center, Origin, Bottom - self.ZJustification = "Origin" # Left, Center, Origin, Right - self.YOffset = 0 - self.ZOffset = 0 - self.rotation = 0 - self.material : Material = None - self.color = BaseOther.color - self.profile_data = None #2D polycurve of the sectionprofile (DOUBLE TO BE REMOVED) - self.profile = None #object of 2D profile - self.colorlst = [] - self.vector = None - self.vector_normalised = None - self.centerbottom = None +class Roundtube(Profile): + def __init__(self, name, d, t): + super().__init__(name, "Round Tube Profile", "IfcCircleHollowProfileDef", d, d) - def props(self): - self.vector = Vector(self.end.x-self.start.x, - self.end.y-self.start.y, self.end.z-self.start.z) - self.vector_normalised = Vector.normalize(self.vector) - self.length = Vector.length(self.vector) + # parameters + self.type = __class__.__name__ + self.r = d / 2 + self.d = d + self.t = t # wall thickness + dr = self.r / sqrt2 # grootste deel + r = self.r + ri = r - t + dri = ri / sqrt2 - @classmethod - def by_startpoint_endpoint(cls, start: Union[Point, Node], end: Union[Point, Node], profile: Union[str, Profile], name: str, material: None, comments=None): - f1 = Frame() - f1.comments = comments + # describe points + p1 = Point2D(r, 0) # right middle + p2 = Point2D(dr, dr) + p3 = Point2D(0, r) # middle top + p4 = Point2D(-dr, dr) + p5 = Point2D(-r, 0) # left middle + p6 = Point2D(-dr, -dr) + p7 = Point2D(0, -r) # middle bottom + p8 = Point2D(dr, -dr) - if start.type == 'Point': - f1.start = start - elif start.type == 'Node': - f1.start = start.point - if end.type == 'Point': - f1.end = end - elif end.type == 'Node': - f1.end = end.point + p9 = Point2D(ri, 0) # right middle inner + p10 = Point2D(dri, dri) + p11 = Point2D(0, ri) # middle top inner + p12 = Point2D(-dri, dri) + p13 = Point2D(-ri, 0) # left middle inner + p14 = Point2D(-dri, -dri) + p15 = Point2D(0, -ri) # middle bottom inner + p16 = Point2D(dri, -dri) - if isinstance(profile,Profile): - f1.curve = profile.curve - f1.profile = profile - elif type(profile).__name__ == "str": - res = nameToProfile(profile) - f1.curve = res.polycurve2d # polycurve2d - f1.points = res.polycurve2d.points - f1.profile = res.profile - else: - print("[by_startpoint_endpoint_profile], input is not correct.") - sys.exit() + # describe curves + l1 = Arc2D(p1, p2, p3) + l2 = Arc2D(p3, p4, p5) + l3 = Arc2D(p5, p6, p7) + l4 = Arc2D(p7, p8, p1) - f1.directionVector = Vector.by_two_points(f1.start, f1.end) - f1.length = Vector.length(f1.directionVector) - f1.name = name - f1.extrusion = Extrusion.by_polycurve_height_vector( - f1.curve, f1.length, CSGlobal, f1.start, f1.directionVector) - f1.extrusion.name = name - f1.curve3d = f1.extrusion.polycurve_3d_translated - f1.profileName = profile - f1.material = material - f1.color = material.colorint - f1.colorlst = colorlist(f1.extrusion, f1.color) - f1.props() - return f1 + l5 = Line2D(p1, p9) - @classmethod - def by_startpoint_endpoint_profile_shapevector(cls, start: Union[Point, Node], end: Union[Point, Node], profile_name: str, name: str, vector2d: Vector2, rotation: float, material: None, comments: None): - f1 = Frame() - f1.comments = comments + l6 = Arc2D(p9, p10, p11) + l7 = Arc2D(p11, p12, p13) + l8 = Arc2D(p13, p14, p15) + l9 = Arc2D(p15, p16, p9) + l10 = Line2D(p9, p1) - if start.type == 'Point': - f1.start = start - elif start.type == 'Node': - f1.start = start.point - if end.type == 'Point': - f1.end = end - elif end.type == 'Node': - f1.end = end.point - - #try: - curv = nameToProfile(profile_name).polycurve2d - #except Exception as e: - # Profile does not exist - #print(f"Profile does not exist: {profile_name}\nError: {e}") + self.curve = PolyCurve2D().by_joined_curves( + [l1, l2, l3, l4, l5, l6, l7, l8, l9, l10]) - f1.rotation = rotation - curvrot = curv.rotate(rotation) # rotation in degrees - f1.curve = curvrot.translate(vector2d) - f1.XOffset = vector2d.x - f1.YOffset = vector2d.y - f1.directionVector = Vector.by_two_points(f1.start, f1.end) - f1.length = Vector.length(f1.directionVector) - f1.name = name - f1.extrusion = Extrusion.by_polycurve_height_vector( - f1.curve, f1.length, CSGlobal, f1.start, f1.directionVector) - f1.extrusion.name = name - f1.curve3d = f1.extrusion.polycurve_3d_translated - f1.profileName = profile_name - f1.material = material - f1.color = material.colorint - f1.colorlst = colorlist(f1.extrusion, f1.color) - f1.props() - return f1 +class LAngle(Profile): + def __init__(self, name, height, width, tw, tf, r1, r2, ex, ey): + super().__init__(name, "LAngle", "IfcLShapeProfileDef", height, width, tw, tf) - @classmethod - def by_startpoint_endpoint_profile_justifiction(cls, start: Union[Point, Node], end: Union[Point, Node], profile: Union[str, PolyCurve2D], name: str, XJustifiction: str, YJustifiction: str, rotation: float, material=None, ey: None = float, ez: None = float, structuralType: None = str, comments=None): - f1 = Frame() - f1.comments = comments + # parameters + self.type = __class__.__name__ - if start.type == 'Point': - f1.start = start - elif start.type == 'Node': - f1.start = start.point - if end.type == 'Point': - f1.end = end - elif end.type == 'Node': - f1.end = end.point + self.r1 = r1 # inner fillet + r11 = r1 / sqrt2 + self.r2 = r2 # outer fillet + r21 = r2 / sqrt2 + self.ex = ex # from left + self.ey = ey # from bottom - f1.structuralType = structuralType - f1.rotation = rotation + # describe points + p1 = Point2D(-ex, -ey) # left bottom + p2 = Point2D(width - ex, -ey) # right bottom + p3 = Point2D(width - ex, -ey + tf - r2) # start arc + p4 = Point2D(width - ex - r2 + r21, -ey + tf - + r2 + r21) # second point arc + p5 = Point2D(width - ex - r2, -ey + tf) # end arc + p6 = Point2D(-ex + tf + r1, -ey + tf) # start arc + p7 = Point2D(-ex + tf + r1 - r11, -ey + tf + + r1 - r11) # second point arc + p8 = Point2D(-ex + tf, -ey + tf + r1) # end arc + p9 = Point2D(-ex + tf, height - ey - r2) # start arc + p10 = Point2D(-ex + tf - r2 + r21, height - ey - + r2 + r21) # second point arc + p11 = Point2D(-ex + tf - r2, height - ey) # end arc + p12 = Point2D(-ex, height - ey) # left top - if type(profile).__name__ == "PolyCurve2D": - profile_name = "None" - f1.profile_data = profile - curve = f1.profile_data - elif type(profile).__name__ == "Polygon": - profile_name = "None" - f1.profile_data = PolyCurve2D.by_points(profile.points) - curve = f1.profile_data - elif type(profile).__name__ == "str": - profile_name = profile - f1.profile_data = nameToProfile(profile).polycurve2d # polycurve2d - curve = f1.profile_data - else: - print("[by_startpoint_endpoint_profile], input is not correct.") - sys.exit() + # describe curves + l1 = Line2D(p1, p2) + l2 = Line2D(p2, p3) + l3 = Arc2D(p3, p4, p5) + l4 = Line2D(p5, p6) + l5 = Arc2D(p6, p7, p8) + l6 = Line2D(p8, p9) + l7 = Arc2D(p9, p10, p11) + l8 = Line2D(p11, p12) + l9 = Line2D(p12, p1) - # curve = f1.profile_data.polycurve2d + self.curve = PolyCurve2D().by_joined_curves( + [l1, l2, l3, l4, l5, l6, l7, l8, l9]) - v1 = justifictionToVector(curve, XJustifiction, YJustifiction) # 1 - f1.XOffset = v1.x - f1.YOffset = v1.y - curve = curve.translate(v1) - curve = curve.translate(Vector2(ey, ez)) # 2 - curve = curve.rotate(f1.rotation) # 3 - f1.curve = curve +class TProfileRounded(Profile): + # ToDo: inner outer fillets in polycurve + def __init__(self, name, height, width, tw, tf, r, r1, r2, ex, ey): + super().__init__(name, "TProfile", "IfcTShapeProfileDef", height, width, tw, tf) - f1.directionVector = Vector.by_two_points(f1.start, f1.end) - f1.length = Vector.length(f1.directionVector) - f1.name = name - f1.extrusion = Extrusion.by_polycurve_height_vector( - f1.curve, f1.length, CSGlobal, f1.start, f1.directionVector) - f1.extrusion.name = name - f1.curve3d = f1.extrusion.polycurve_3d_translated - try: - pnew = PolyCurve.by_joined_curves(f1.curve3d.curves) - f1.centerbottom = PolyCurve.centroid(pnew) - except: - pass + self.r = r # inner fillet + self.r01 = r / sqrt2 + self.r1 = r1 # outer fillet flange + r11 = r1 / sqrt2 + self.r2 = r2 # outer fillet top web + r21 = r2 / sqrt2 + self.ex = ex # from left + self.ey = ey # from bottom - f1.profileName = profile_name - f1.material = material - f1.color = material.colorint - f1.colorlst = colorlist(f1.extrusion, f1.color) - f1.props() - return f1 + # describe points + p1 = Point2D(-ex, -ey) # left bottom + p2 = Point2D(width - ex, -ey) # right bottom + p3 = Point2D(width - ex, -ey + tf - r1) # start arc + p4 = Point2D(width - ex - r1 + r11, -ey + tf - + r1 + r11) # second point arc + p5 = Point2D(width - ex - r1, -ey + tf) # end arc + p6 = Point2D(0.5 * tw + r, -ey + tf) # start arc + p7 = Point2D(0.5 * tw + r - self.r01, -ey + tf + + r - self.r01) # second point arc + p8 = Point2D(0.5 * tw, -ey + tf + r) # end arc + p9 = Point2D(0.5 * tw, -ey + height - r2) # start arc + p10 = Point2D(0.5 * tw - r21, -ey + height - + r2 + r21) # second point arc + p11 = Point2D(0.5 * tw - r2, -ey + height) # end arc - @classmethod - def by_startpoint_endpoint_rect(cls, start: Union[Point, Node], end: Union[Point, Node], width: float, height: float, name: str, rotation: float, material=None, comments=None): - # 2D polycurve - f1 = Frame() - f1.comments = comments + p12 = Point2D(-p11.x, p11.y) + p13 = Point2D(-p10.x, p10.y) + p14 = Point2D(-p9.x, p9.y) + p15 = Point2D(-p8.x, p8.y) + p16 = Point2D(-p7.x, p7.y) + p17 = Point2D(-p6.x, p6.y) + p18 = Point2D(-p5.x, p5.y) + p19 = Point2D(-p4.x, p4.y) + p20 = Point2D(-p3.x, p3.y) - if start.type == 'Point': - f1.start = start - elif start.type == 'Node': - f1.start = start.point - if end.type == 'Point': - f1.end = end - elif end.type == 'Node': - f1.end = end.point + # describe curves + l1 = Line2D(p1, p2) - f1.directionVector = Vector.by_two_points(f1.start, f1.end) - f1.length = Vector.length(f1.directionVector) - f1.name = name + l2 = Line2D(p2, p3) + l3 = Arc2D(p3, p4, p5) + l4 = Line2D(p5, p6) + l5 = Arc2D(p6, p7, p8) + l6 = Line2D(p8, p9) + l7 = Arc2D(p9, p10, p11) + l8 = Line2D(p11, p12) - prof = Rectangle(str(width)+"x"+str(height),width,height) - polycurve = prof.curve - f1.profile = prof - curvrot = polycurve.rotate(rotation) - f1.extrusion = Extrusion.by_polycurve_height_vector( - curvrot, f1.length, CSGlobal, f1.start, f1.directionVector) - f1.extrusion.name = name - f1.curve3d = curvrot - f1.profileName = name - f1.material = material - f1.color = material.colorint - f1.colorlst = colorlist(f1.extrusion, f1.color) - f1.props() - return f1 - - - @classmethod - def by_point_height_rotation(cls, start: Union[Point, Node], height: float, polycurve: PolyCurve2D, frame_name: str, rotation: float, material=None, comments=None): - # 2D polycurve - f1 = Frame() - f1.comments = comments - - if start.type == 'Point': - f1.start = start - elif start.type == 'Node': - f1.start = start.point - - f1.end = Point.translate(f1.start, Vector(0, 0.00001, height)) + l9 = Arc2D(p12, p13, p14) + l10 = Line2D(p14, p15) + l11 = Arc2D(p15, p16, p17) + l12 = Line2D(p17, p18) + l13 = Arc2D(p18, p19, p20) + l14 = Line2D(p20, p1) - # self.curve = Line(start, end) - f1.directionVector = Vector.by_two_points(f1.start, f1.end) - f1.length = Vector.length(f1.directionVector) - f1.name = frame_name - f1.profileName = frame_name - curvrot = polycurve.rotate(rotation) # rotation in degrees - f1.extrusion = Extrusion.by_polycurve_height_vector( - curvrot, f1.length, CSGlobal, f1.start, f1.directionVector) - f1.extrusion.name = frame_name - f1.curve3d = curvrot - f1.material = material - f1.color = material.colorint - f1.colorlst = colorlist(f1.extrusion, f1.color) - f1.props() - return f1 + self.curve = PolyCurve2D().by_joined_curves( + [l1, l2, l3, l4, l5, l6, l7, l8, l9, l10, l11, l12, l13, l14]) - @classmethod - def by_point_profile_height_rotation(cls, start: Union[Point, Node], height: float, profile_name: str, rotation: float, material=None, comments=None): - f1 = Frame() - f1.comments = comments +class RectangleHollowSection(Profile): + def __init__(self, name, height, width, t, r1, r2): + super().__init__(name, "Rectangle Hollow Section", "IfcRectangleHollowProfileDef", height, width, tw=t, tf=t) - if start.type == 'Point': - f1.start = start - elif start.type == 'Node': - f1.start = start.point - # TODO vertical column not possible - f1.end = Point.translate(f1.start, Vector(0, height)) + # parameters + self.type = __class__.__name__ - # self.curve = Line(start, end) - f1.directionVector = Vector.by_two_points(f1.start, f1.end) - f1.length = Vector.length(f1.directionVector) - f1.name = profile_name - f1.profileName = profile_name - curv = nameToProfile(profile_name).polycurve2d - curvrot = curv.rotate(rotation) # rotation in degrees - f1.extrusion = Extrusion.by_polycurve_height_vector( - curvrot.curves, f1.length, CSGlobal, f1.start, f1.directionVector) - f1.extrusion.name = profile_name - f1.curve3d = curvrot - f1.profileName = profile_name - f1.material = material - f1.color = material.colorint - f1.colorlst = colorlist(f1.extrusion, f1.color) - f1.props() - return f1 + self.t = t # thickness + self.r1 = r1 # outer radius + self.r2 = r2 # inner radius + dr = r1 - r1 / sqrt2 + dri = r2 - r2 / sqrt2 + bi = width - t + hi = height - t - @classmethod - def by_startpoint_endpoint_curve_justifiction(cls, start: Union[Point, Node], end: Union[Point, Node], polycurve: PolyCurve2D, name: str, XJustifiction: str, YJustifiction: str, rotation: float, material=None, comments=None): - f1 = Frame() - f1.comments = comments + # describe points + p1 = Point2D(-width / 2 + r1, - height / 2) # left bottom end arc + p2 = Point2D(width / 2 - r1, - height / 2) # right bottom start arc + p3 = Point2D(width / 2 - dr, - height / 2 + dr) # right bottom mid arc + p4 = Point2D(width / 2, - height / 2 + r1) # right bottom end arc + p5 = Point2D(p4.x, -p4.y) # right start arc + p6 = Point2D(p3.x, -p3.y) # right mid arc + p7 = Point2D(p2.x, -p2.y) # right end arc + p8 = Point2D(-p7.x, p7.y) # left start arc + p9 = Point2D(-p6.x, p6.y) # left mid arc + p10 = Point2D(-p5.x, p5.y) # left end arc + p11 = Point2D(p10.x, -p10.y) # right bottom start arc + p12 = Point2D(p9.x, -p9.y) # right bottom mid arc - if start.type == 'Point': - f1.start = start - elif start.type == 'Node': - f1.start = start.point - if end.type == 'Point': - f1.end = end - elif end.type == 'Node': - f1.end = end.point + # inner part + p13 = Point2D(-bi / 2 + r2, - hi / 2) # left bottom end arc + p14 = Point2D(bi / 2 - r2, - hi / 2) # right bottom start arc + p15 = Point2D(bi / 2 - dri, - hi / 2 + dri) # right bottom mid arc + p16 = Point2D(bi / 2, - hi / 2 + r2) # right bottom end arc + p17 = Point2D(p16.x, -p16.y) # right start arc + p18 = Point2D(p15.x, -p15.y) # right mid arc + p19 = Point2D(p14.x, -p14.y) # right end arc + p20 = Point2D(-p19.x, p19.y) # left start arc + p21 = Point2D(-p18.x, p18.y) # left mid arc + p22 = Point2D(-p17.x, p17.y) # left end arc + p23 = Point2D(p22.x, -p22.y) # right bottom start arc + p24 = Point2D(p21.x, -p21.y) # right bottom mid arc - f1.rotation = rotation - curv = polycurve - curvrot = curv.rotate(rotation) # rotation in degrees - # center, left, right, origin / center, top bottom, origin - v1 = justifictionToVector(curvrot, XJustifiction, YJustifiction) - f1.XOffset = v1.x - f1.YOffset = v1.y - f1.curve = curv.translate(v1) - f1.directionVector = Vector.by_two_points(f1.start, f1.end) - f1.length = Vector.length(f1.directionVector) - f1.name = name - f1.extrusion = Extrusion.by_polycurve_height_vector( - f1.curve.curves, f1.length, CSGlobal, f1.start, f1.directionVector) - f1.extrusion.name = name - f1.profileName = "none" - f1.material = material - f1.color = material.colorint - f1.colorlst = colorlist(f1.extrusion, f1.color) - f1.props() - return f1 + # describe outer curves + l1 = Line2D(p1, p2) + l2 = Arc2D(p2, p3, p4) + l3 = Line2D(p4, p5) + l4 = Arc2D(p5, p6, p7) + l5 = Line2D(p7, p8) + l6 = Arc2D(p8, p9, p10) + l7 = Line2D(p10, p11) + l8 = Arc2D(p11, p12, p1) - def write(self, project): - project.objects.append(self) - return self + l9 = Line2D(p1, p13) + # describe inner curves + l10 = Line2D(p13, p14) + l11 = Arc2D(p14, p15, p16) + l12 = Line2D(p16, p17) + l13 = Arc2D(p17, p18, p19) + l14 = Line2D(p19, p20) + l15 = Arc2D(p20, p21, p22) + l16 = Line2D(p22, p23) + l17 = Arc2D(p23, p24, p13) -sqrt2 = math.sqrt(2) + l18 = Line2D(p13, p1) + self.curve = PolyCurve2D().by_joined_curves( + [l1, l2, l3, l4, l5, l6, l7, l8, l9, l10, l11, l12, l13, l14, l15, l16, l17, l18]) -# Hierachie: -# point 2D -# line 2D -# PolyCurve2D 2D -# profile is een parametrische vorm heeft als resultaat een 2D curve -# section is een profiel met eigenschappen HEA200, 200,200,10,10,5 en eventuele rekenkundige eigenschappen. -# beam is een object wat in 3D zit met materiaal enz. +class CProfile(Profile): + def __init__(self, name, width, height, t, r1, ex): + super().__init__(name, "Cold Formed C Profile", "Unknown", height, width, tw=t, tf=t) -class Profile(Serializable): + # parameters + self.type = __class__.__name__ - def __init__(self, name: str, description: str, IFC_profile_def: str, height: float, width: float, - tw: float = None, tf: float = None): - """Creates a profile profile. + self.t = t # flange thickness + self.r1 = r1 # outer radius + self.r2 = r1 - t # inner radius + r2 = r1 - t - Args: - name (str): _description_ - description (str): _description_ - IFC_profile_def (str): _description_ - height (_type_): _description_ - width (_type_): _description_ - """ + self.ex = ex + self.ey = height / 2 + dr = r1 - r1 / sqrt2 + dri = r2 - r2 / sqrt2 + hi = height - t - self.IFC_profile_def = IFC_profile_def - self.ID = generateID() - self.name = name - self.description = description - self.curve = [] - self.height = height - self.width = width - self.tw = tw - self.tf = tf - self.type = None + # describe points + p1 = Point2D(width - ex, -height / 2) # right bottom + p2 = Point2D(r1 - ex, -height / 2) + p3 = Point2D(dr - ex, -height / 2 + dr) + p4 = Point2D(0 - ex, -height / 2 + r1) + p5 = Point2D(p4.x, -p4.y) + p6 = Point2D(p3.x, -p3.y) + p7 = Point2D(p2.x, -p2.y) + p8 = Point2D(p1.x, -p1.y) # right top + p9 = Point2D(width - ex, hi / 2) # right top inner + p10 = Point2D(t + r2 - ex, hi / 2) + p11 = Point2D(t + dri - ex, hi / 2 - dri) + p12 = Point2D(t - ex, hi / 2 - r2) + p13 = Point2D(p12.x, -p12.y) + p14 = Point2D(p11.x, -p11.y) + p15 = Point2D(p10.x, -p10.y) + p16 = Point2D(p9.x, -p9.y) # right bottom inner + # describe outer curves + l1 = Line2D(p1, p2) # bottom + l2 = Arc2D(p2, p3, p4) # right outer fillet + l3 = Line2D(p4, p5) # left outer web + l4 = Arc2D(p5, p6, p7) # left top outer fillet + l5 = Line2D(p7, p8) # outer top + l6 = Line2D(p8, p9) + l7 = Line2D(p9, p10) + l8 = Arc2D(p10, p11, p12) # left top inner fillet + l9 = Line2D(p12, p13) + l10 = Arc2D(p13, p14, p15) # left botom inner fillet + l11 = Line2D(p15, p16) + l12 = Line2D(p16, p1) - def __str__(self): - return f"{self.type} ({self.name})" + self.curve = PolyCurve2D().by_joined_curves( + [l1, l2, l3, l4, l5, l6, l7, l8, l9, l10, l11, l12]) -class CChannelParallelFlange(Profile): - def __init__(self, name, height, width, tw, tf, r, ex): - super().__init__(name, "C-channel with parallel flange", "IfcUShapeProfileDef", height, width, tw, tf) +class CProfileWithLips(Profile): + def __init__(self, name, width, height, h1, t, r1, ex): + super().__init__(name, "Cold Formed C Profile with Lips", "Unknown", height, width, tw=t, tf=t) # parameters self.type = __class__.__name__ - self.r1 = r # web fillet - self.ex = ex # centroid horizontal + self.h1 = h1 # lip length + self.t = t # flange thickness + self.r1 = r1 # outer radius + self.r2 = r1 - t # inner radius + r2 = r1 - t + + self.ex = ex + self.ey = height / 2 + dr = r1 - r1 / sqrt2 + dri = r2 - r2 / sqrt2 + hi = height - t # describe points - p1 = Point(-ex, -height / 2) # left bottom - p2 = Point(width - ex, -height / 2) # right bottom - p3 = Point(width - ex, -height / 2 + tf) - p4 = Point(-ex + tw + r, -height / 2 + tf) # start arc - p5 = Point(-ex + tw + r, -height / 2 + tf + r) # second point arc - p6 = Point(-ex + tw, -height / 2 + tf + r) # end arc - p7 = Point(-ex + tw, height / 2 - tf - r) # start arc - p8 = Point(-ex + tw + r, height / 2 - tf - r) # second point arc - p9 = Point(-ex + tw + r, height / 2 - tf) # end arc - p10 = Point(width - ex, height / 2 - tf) - p11 = Point(width - ex, height / 2) # right top - p12 = Point(-ex, height / 2) # left top + p1 = Point2D(width - ex - r1, -height / 2) # right bottom before fillet + p2 = Point2D(r1 - ex, -height / 2) + p3 = Point2D(dr - ex, -height / 2 + dr) + p4 = Point2D(0 - ex, -height / 2 + r1) + p5 = Point2D(p4.x, -p4.y) + p6 = Point2D(p3.x, -p3.y) + p7 = Point2D(p2.x, -p2.y) + p8 = Point2D(p1.x, -p1.y) # right top before fillet + p9 = Point2D(width - ex - dr, height / 2 - dr) # middle point arc + p10 = Point2D(width - ex, height / 2 - r1) # end fillet + p11 = Point2D(width - ex, height / 2 - h1) + p12 = Point2D(width - ex - t, height / 2 - h1) # bottom lip + p13 = Point2D(width - ex - t, height / 2 - t - r2) # start inner fillet right top + p14 = Point2D(width - ex - t - dri, height / 2 - t - dri) + p15 = Point2D(width - ex - t - r2, height / 2 - t) # end inner fillet right top + p16 = Point2D(0 - ex + t + r2, height / 2 - t) + p17 = Point2D(0 - ex + t + dri, height / 2 - t - dri) + p18 = Point2D(0 - ex + t, height / 2 - t - r2) - # describe curves - l1 = Line(p1, p2) - l2 = Line(p2, p3) - l3 = Line(p3, p4) - l4 = Arc2D(p4, p5, p6) - l5 = Line(p6, p7) - l6 = Arc2D(p7, p8, p9) - l7 = Line(p9, p10) - l8 = Line(p10, p11) - l9 = Line(p11, p12) - l10 = Line(p12, p1) + p19 = Point2D(p18.x, -p18.y) + p20 = Point2D(p17.x, -p17.y) + p21 = Point2D(p16.x, -p16.y) + p22 = Point2D(p15.x, -p15.y) + p23 = Point2D(p14.x, -p14.y) + p24 = Point2D(p13.x, -p13.y) + p25 = Point2D(p12.x, -p12.y) + p26 = Point2D(p11.x, -p11.y) + p27 = Point2D(p10.x, -p10.y) + p28 = Point2D(p9.x, -p9.y) - self.curve = PolyCurve.by_joined_curves( - [l1, l2, l3, l4, l5, l6, l7, l8, l9, l10]) + # describe outer curves + l1 = Line2D(p1, p2) + l2 = Arc2D(p2, p3, p4) + l3 = Line2D(p4, p5) + l4 = Arc2D(p5, p6, p7) # outer fillet right top + l5 = Line2D(p7, p8) + l6 = Arc2D(p8, p9, p10) + l7 = Line2D(p10, p11) + l8 = Line2D(p11, p12) + l9 = Line2D(p12, p13) + l10 = Arc2D(p13, p14, p15) + l11 = Line2D(p15, p16) + l12 = Arc2D(p16, p17, p18) + l13 = Line2D(p18, p19) # inner web + l14 = Arc2D(p19, p20, p21) + l15 = Line2D(p21, p22) + l16 = Arc2D(p22, p23, p24) + l17 = Line2D(p24, p25) + l18 = Line2D(p25, p26) + l19 = Line2D(p26, p27) + l20 = Arc2D(p27, p28, p1) -class CChannelSlopedFlange(Profile): - def __init__(self, name, height, width, tw, tf, r1, r2, tl, sa, ex): - super().__init__(name, "C-channel with sloped flange", "IfcUShapeProfileDef", height, width, tw, tf) + self.curve = PolyCurve2D().by_joined_curves( + [l1, l2, l3, l4, l5, l6, l7, l8, l9, l10, l11, l12, l13, l14, l15, l16, l17, l18, l19, l20]) - self.r1 = r1 # web fillet - r11 = r1 / sqrt2 - self.r2 = r2 # flange fillet - r21 = r2 / sqrt2 - self.tl = tl # flange thickness location from right - self.sa = math.radians(sa) # the angle of sloped flange in degrees - self.ex = ex # centroid horizontal +class LProfileColdFormed(Profile): + def __init__(self, name, width, height, t, r1, ex, ey): + super().__init__(name, "Cold Formed L Profile", "Unknown", height, width, tw=t, tf=t) + + # parameters + self.type = __class__.__name__ + + self.t = t # flange thickness + self.r1 = r1 # inner radius + self.r2 = r1 - t # outer radius + self.ex = ex + self.ey = ey + r11 = r1 / math.sqrt(2) + r2 = r1 + t + r21 = r2 / math.sqrt(2) # describe points - p1 = Point2D(-ex, -height / 2) # left bottom - p2 = Point2D(width - ex, -height / 2) # right bottom - p3 = Point2D(width - ex, -height / 2 + tf - math.tan(self.sa) - * tl - r2) # start arc - p4 = Point2D(width - ex - r2 + r21, -height / 2 + tf - - math.tan(self.sa) * tl - r2 + r21) # second point arc - p5 = Point2D(width - ex - r2 + math.sin(self.sa) * r2, -height / - 2 + tf - math.tan(self.sa) * (tl - r2)) # end arc - p6 = Point2D(-ex + tw + r1 - math.sin(self.sa) * r1, -height / 2 + - tf + math.tan(self.sa) * (width - tl - tw - r1)) # start arc - p7 = Point2D(-ex + tw + r1 - r11, -height / 2 + tf + math.tan(self.sa) - * (width - tl - tw - r1) + r1 - r11) # second point arc - p8 = Point2D(-ex + tw, -height / 2 + tf + math.tan(self.sa) - * (width - tl - tw) + r1) # end arc - p9 = Point2D(p8.x, -p8.y) # start arc - p10 = Point2D(p7.x, -p7.y) # second point arc - p11 = Point2D(p6.x, -p6.y) # end arc - p12 = Point2D(p5.x, -p5.y) # start arc - p13 = Point2D(p4.x, -p4.y) # second point arc - p14 = Point2D(p3.x, -p3.y) # end arc - p15 = Point2D(p2.x, -p2.y) # right top - p16 = Point2D(p1.x, -p1.y) # left top + p1 = Point2D(-ex, -ey + r2) # start arc left bottom + p2 = Point2D(-ex + r2 - r21, -ey + r2 - r21) # second point arc + p3 = Point2D(-ex + r2, -ey) # end arc + p4 = Point2D(width - ex, -ey) # right bottom + p5 = Point2D(width - ex, -ey + t) + p6 = Point2D(-ex + t + r1, -ey + t) # start arc + p7 = Point2D(-ex + t + r1 - r11, -ey + t + + r1 - r11) # second point arc + p8 = Point2D(-ex + t, -ey + t + r1) # end arc + p9 = Point2D(-ex + t, ey) + p10 = Point2D(-ex, ey) # left top - # describe curves - l1 = Line2D(p1, p2) - l2 = Line2D(p2, p3) - l3 = Arc2D(p3, p4, p5) + l1 = Arc2D(p1, p2, p3) + l2 = Line2D(p3, p4) + l3 = Line2D(p4, p5) l4 = Line2D(p5, p6) l5 = Arc2D(p6, p7, p8) l6 = Line2D(p8, p9) - l7 = Arc2D(p9, p10, p11) - l8 = Line2D(p11, p12) - l9 = Arc2D(p12, p13, p14) - l10 = Line2D(p14, p15) - l11 = Line2D(p15, p16) - l12 = Line2D(p16, p1) + l7 = Line2D(p9, p10) + l8 = Line2D(p10, p1) self.curve = PolyCurve2D().by_joined_curves( - [l1, l2, l3, l4, l5, l6, l7, l8, l9, l10, l11, l12]) + [l1, l2, l3, l4, l5, l6, l7, l8]) -class IShapeParallelFlange(Profile): - def __init__(self, name, height, width, tw, tf, r): - super().__init__(name, "I Shape profile with parallel flange", "IfcUShapeProfileDef", height, width, tw, - tf) +class SigmaProfileWithLipsColdFormed(Profile): + def __init__(self, name, width, height, t, r1, h1, h2, h3, b2, ex): + super().__init__(name, "Cold Formed Sigma Profile with Lips", "Unknown", height, width, tw=t, tf=t) + # parameters + self.type = __class__.__name__ - self.r = r # web fillet - self.r1 = r1 = r / sqrt2 + self.h1 = h1 # LipLength + self.h2 = h2 # MiddleBendLength + self.h3 = h3 # TopBendLength + self.h4 = h4 = (height - h2 - h3 * 2) / 2 + self.h5 = h5 = math.tan(0.5 * math.atan(b2 / h4)) * t + self.b2 = b2 # MiddleBendWidth + self.t = t # flange thickness + self.r1 = r1 # inner radius + self.r2 = r2 = r1 + t # outer radius + self.ex = ex + self.ey = ey = height / 2 + r11 = r11 = r1 / math.sqrt(2) + r21 = r21 = r2 / math.sqrt(2) - # describe points - p1 = Point2D(width / 2, -height / 2) # right bottom - p2 = Point2D(width / 2, -height / 2 + tf) - p3 = Point2D(tw / 2 + r, -height / 2 + tf) # start arc - # second point arc - p4 = Point2D(tw / 2 + r - r1, (-height / 2 + tf + r - r1)) - p5 = Point2D(tw / 2, -height / 2 + tf + r) # end arc - p6 = Point2D(tw / 2, height / 2 - tf - r) # start arc - p7 = Point2D(tw / 2 + r - r1, height / 2 - tf - r + r1) # second point arc - p8 = Point2D(tw / 2 + r, height / 2 - tf) # end arc - p9 = Point2D(width / 2, height / 2 - tf) - p10 = Point2D((width / 2), (height / 2)) # right top - p11 = Point2D(-p10.x, p10.y) # left top - p12 = Point2D(-p9.x, p9.y) - p13 = Point2D(-p8.x, p8.y) # start arc - p14 = Point2D(-p7.x, p7.y) # second point arc - p15 = Point2D(-p6.x, p6.y) # end arc - p16 = Point2D(-p5.x, p5.y) # start arc - p17 = Point2D(-p4.x, p4.y) # second point arc - p18 = Point2D(-p3.x, p3.y) # end arc - p19 = Point2D(-p2.x, p2.y) - p20 = Point2D(-p1.x, p1.y) + p1 = Point2D(-ex + b2, -h2 / 2) + p2 = Point2D(-ex, -ey + h3) + p3 = Point2D(-ex, -ey + r2) # start arc left bottom + p4 = Point2D(-ex + r2 - r21, -ey + r2 - r21) # second point arc + p5 = Point2D(-ex + r2, -ey) # end arc + p6 = Point2D(width - ex - r2, -ey) # start arc + p7 = Point2D(width - ex - r2 + r21, -ey + r2 - r21) # second point arc + p8 = Point2D(width - ex, -ey + r2) # end arc + p9 = Point2D(width - ex, -ey + h1) # end lip + p10 = Point2D(width - ex - t, -ey + h1) + p11 = Point2D(width - ex - t, -ey + t + r1) # start arc + p12 = Point2D(width - ex - t - r1 + r11, -ey + + t + r1 - r11) # second point arc + p13 = Point2D(width - ex - t - r1, -ey + t) # end arc + p14 = Point2D(-ex + t + r1, -ey + t) # start arc + p15 = Point2D(-ex + t + r1 - r11, -ey + t + + r1 - r11) # second point arc + p16 = Point2D(-ex + t, -ey + t + r1) # end arc + p17 = Point2D(-ex + t, -ey + h3 - h5) + p18 = Point2D(-ex + b2 + t, -h2 / 2 - h5) + p19 = Point2D(p18.x, -p18.y) + p20 = Point2D(p17.x, -p17.y) + p21 = Point2D(p16.x, -p16.y) + p22 = Point2D(p15.x, -p15.y) + p23 = Point2D(p14.x, -p14.y) + p24 = Point2D(p13.x, -p13.y) + p25 = Point2D(p12.x, -p12.y) + p26 = Point2D(p11.x, -p11.y) + p27 = Point2D(p10.x, -p10.y) + p28 = Point2D(p9.x, -p9.y) + p29 = Point2D(p8.x, -p8.y) + p30 = Point2D(p7.x, -p7.y) + p31 = Point2D(p6.x, -p6.y) + p32 = Point2D(p5.x, -p5.y) + p33 = Point2D(p4.x, -p4.y) + p34 = Point2D(p3.x, -p3.y) + p35 = Point2D(p2.x, -p2.y) + p36 = Point2D(p1.x, -p1.y) - # describe curves l1 = Line2D(p1, p2) l2 = Line2D(p2, p3) l3 = Arc2D(p3, p4, p5) @@ -7382,835 +7340,904 @@ def __init__(self, name, height, width, tw, tf, r): l6 = Line2D(p8, p9) l7 = Line2D(p9, p10) l8 = Line2D(p10, p11) - l9 = Line2D(p11, p12) - l10 = Line2D(p12, p13) - l11 = Arc2D(p13, p14, p15) - l12 = Line2D(p15, p16) - l13 = Arc2D(p16, p17, p18) + l9 = Arc2D(p11, p12, p13) + l10 = Line2D(p13, p14) + l11 = Arc2D(p14, p15, p16) + l12 = Line2D(p16, p17) + l13 = Line2D(p17, p18) l14 = Line2D(p18, p19) l15 = Line2D(p19, p20) - l16 = Line2D(p20, p1) - - self.curve = PolyCurve2D().by_joined_curves( - [l1, l2, l3, l4, l5, l6, l7, l8, l9, l10, l11, l12, l13, l14, l15, l16]) - -class Rectangle(Profile): - def __init__(self, name, width, height): - super().__init__(name, "Rectangle", "IfcRectangleProfileDef", height, width) - - - # describe points - p1 = Point2D(width / 2, -height / 2) # right bottom - p2 = Point2D(width / 2, height / 2) # right top - p3 = Point2D(-width / 2, height / 2) # left top - p4 = Point2D(-width / 2, -height / 2) # left bottom - - # describe curves - l1 = Line2D(p1, p2) - l2 = Line2D(p2, p3) - l3 = Line2D(p3, p4) - l4 = Line2D(p4, p1) + l16 = Line2D(p20, p21) + l17 = Arc2D(p21, p22, p23) + l18 = Line2D(p23, p24) + l19 = Arc2D(p24, p25, p26) + l20 = Line2D(p26, p27) + l21 = Line2D(p27, p28) + l22 = Line2D(p28, p29) + l23 = Arc2D(p29, p30, p31) + l24 = Line2D(p31, p32) + l25 = Arc2D(p32, p33, p34) + l26 = Line2D(p34, p35) + l27 = Line2D(p35, p36) + l28 = Line2D(p36, p1) - self.curve = PolyCurve2D().by_joined_curves([l1, l2, l3, l4]) + self.curve = PolyCurve2D().by_joined_curves( + [l1, l2, l3, l4, l5, l6, l7, l8, l9, l10, l11, l12, l13, l14, l15, l16, l17, l18, l19, l20, l21, l22, l23, + l24, l25, + l26, l27, l28]) -class Round(Profile): - def __init__(self, name, r): - super().__init__(name, "Round", "IfcCircleProfileDef", r*2, r*2) +class ZProfileColdFormed(Profile): + def __init__(self, name, width, height, t, r1): + super().__init__(name, "Cold Formed Z Profile", "Unknown", height, width, tw=t, tf=t) - self.r = r + # parameters + self.type = __class__.__name__ - dr = r / sqrt2 # grootste deel + self.t = t # flange thickness + self.r1 = r1 # inner radius + self.r2 = r2 = r1 + t # outer radius + self.ex = ex = width / 2 + self.ey = ey = height / 2 + r11 = r11 = r1 / math.sqrt(2) + r21 = r21 = r2 / math.sqrt(2) - # describe points - p1 = Point2D(r, 0) # right middle - p2 = Point2D(dr, dr) - p3 = Point2D(0, r) # middle top - p4 = Point2D(-dr, dr) - p5 = Point2D(-r, 0) # left middle - p6 = Point2D(-dr, -dr) - p7 = Point2D(0, -r) # middle bottom - p8 = Point2D(dr, -dr) + p1 = Point2D(-0.5 * t, -ey + t + r1) # start arc + p2 = Point2D(-0.5 * t - r1 + r11, -ey + t + + r1 - r11) # second point arc + p3 = Point2D(-0.5 * t - r1, -ey + t) # end arc + p4 = Point2D(-ex, -ey + t) + p5 = Point2D(-ex, -ey) # left bottom + p6 = Point2D(-r2 + 0.5 * t, -ey) # start arc + p7 = Point2D(-r2 + 0.5 * t + r21, -ey + r2 - r21) # second point arc + p8 = Point2D(0.5 * t, -ey + r2) # end arc + p9 = Point2D(-p1.x, -p1.y) + p10 = Point2D(-p2.x, -p2.y) + p11 = Point2D(-p3.x, -p3.y) + p12 = Point2D(-p4.x, -p4.y) + p13 = Point2D(-p5.x, -p5.y) + p14 = Point2D(-p6.x, -p6.y) + p15 = Point2D(-p7.x, -p7.y) + p16 = Point2D(-p8.x, -p8.y) - # describe curves l1 = Arc2D(p1, p2, p3) - l2 = Arc2D(p3, p4, p5) - l3 = Arc2D(p5, p6, p7) - l4 = Arc2D(p7, p8, p1) + l2 = Line2D(p3, p4) + l3 = Line2D(p4, p5) + l4 = Line2D(p5, p6) + l5 = Arc2D(p6, p7, p8) + l6 = Line2D(p8, p9) + l7 = Arc2D(p9, p10, p11) + l8 = Line2D(p11, p12) + l9 = Line2D(p12, p13) + l10 = Line2D(p13, p14) + l11 = Arc2D(p14, p15, p16) + l12 = Line2D(p16, p1) - self.curve = PolyCurve2D().by_joined_curves([l1, l2, l3, l4]) + self.curve = PolyCurve2D().by_joined_curves( + [l1, l2, l3, l4, l5, l6, l7, l8, l9, l10, l11, l12]) -class Roundtube(Profile): - def __init__(self, name, d, t): - super().__init__(name, "Round Tube Profile", "IfcCircleHollowProfileDef", d, d) +class ZProfileWithLipsColdFormed(Profile): + def __init__(self, name, width, height, t, r1, h1): + super().__init__(name, "Cold Formed Z Profile with Lips", "Unknown", height, width, tw=t, tf=t) # parameters self.type = __class__.__name__ - self.r = d / 2 - self.d = d - self.t = t # wall thickness - dr = self.r / sqrt2 # grootste deel - r = self.r - ri = r - t - dri = ri / sqrt2 - # describe points - p1 = Point2D(r, 0) # right middle - p2 = Point2D(dr, dr) - p3 = Point2D(0, r) # middle top - p4 = Point2D(-dr, dr) - p5 = Point2D(-r, 0) # left middle - p6 = Point2D(-dr, -dr) - p7 = Point2D(0, -r) # middle bottom - p8 = Point2D(dr, -dr) + self.t = t # flange thickness + self.h1 = h1 # lip length + self.r1 = r1 # inner radius + self.r2 = r2 = r1 + t # outer radius + self.ex = ex = width / 2 + self.ey = ey = height / 2 + r11 = r11 = r1 / math.sqrt(2) + r21 = r21 = r2 / math.sqrt(2) - p9 = Point2D(ri, 0) # right middle inner - p10 = Point2D(dri, dri) - p11 = Point2D(0, ri) # middle top inner - p12 = Point2D(-dri, dri) - p13 = Point2D(-ri, 0) # left middle inner - p14 = Point2D(-dri, -dri) - p15 = Point2D(0, -ri) # middle bottom inner - p16 = Point2D(dri, -dri) + p1 = Point2D(-0.5 * t, -ey + t + r1) # start arc + p2 = Point2D(-0.5 * t - r1 + r11, -ey + t + r1 - r11) # second point arc + p3 = Point2D(-0.5 * t - r1, -ey + t) # end arc + p4 = Point2D(-ex + t + r1, -ey + t) # start arc + p5 = Point2D(-ex + t + r1 - r11, -ey + t + r1 - r11) # second point arc + p6 = Point2D(-ex + t, -ey + t + r1) # end arc + p7 = Point2D(-ex + t, -ey + h1) + p8 = Point2D(-ex, -ey + h1) + p9 = Point2D(-ex, -ey + r2) # start arc + p10 = Point2D(-ex + r2 - r21, -ey + r2 - r21) # second point arc + p11 = Point2D(-ex + r2, -ey) # end arc + p12 = Point2D(-r2 + 0.5 * t, -ey) # start arc + p13 = Point2D(-r2 + 0.5 * t + r21, -ey + r2 - r21) # second point arc + p14 = Point2D(0.5 * t, -ey + r2) # end arc + p15 = Point2D(-p1.x, -p1.y) + p16 = Point2D(-p2.x, -p2.y) + p17 = Point2D(-p3.x, -p3.y) + p18 = Point2D(-p4.x, -p4.y) + p19 = Point2D(-p5.x, -p5.y) + p20 = Point2D(-p6.x, -p6.y) + p21 = Point2D(-p7.x, -p7.y) + p22 = Point2D(-p8.x, -p8.y) + p23 = Point2D(-p9.x, -p9.y) + p24 = Point2D(-p10.x, -p10.y) + p25 = Point2D(-p11.x, -p11.y) + p26 = Point2D(-p12.x, -p12.y) + p27 = Point2D(-p13.x, -p13.y) + p28 = Point2D(-p14.x, -p14.y) - # describe curves l1 = Arc2D(p1, p2, p3) - l2 = Arc2D(p3, p4, p5) - l3 = Arc2D(p5, p6, p7) - l4 = Arc2D(p7, p8, p1) - - l5 = Line2D(p1, p9) - - l6 = Arc2D(p9, p10, p11) - l7 = Arc2D(p11, p12, p13) - l8 = Arc2D(p13, p14, p15) - l9 = Arc2D(p15, p16, p9) - l10 = Line2D(p9, p1) + l2 = Line2D(p3, p4) + l3 = Arc2D(p4, p5, p6) + l4 = Line2D(p6, p7) + l5 = Line2D(p7, p8) + l6 = Line2D(p8, p9) + l7 = Arc2D(p9, p10, p11) + l8 = Line2D(p11, p12) + l9 = Arc2D(p12, p13, p14) + l10 = Line2D(p14, p15) + l11 = Arc2D(p15, p16, p17) + l12 = Line2D(p17, p18) + l13 = Arc2D(p18, p19, p20) + l14 = Line2D(p20, p21) + l15 = Line2D(p21, p22) + l16 = Line2D(p22, p23) + l17 = Arc2D(p23, p24, p25) + l18 = Line2D(p25, p26) + l19 = Arc2D(p26, p27, p28) + l20 = Line2D(p28, p1) self.curve = PolyCurve2D().by_joined_curves( - [l1, l2, l3, l4, l5, l6, l7, l8, l9, l10]) + [l1, l2, l3, l4, l5, l6, l7, l8, l9, l10, l11, l12, l13, l14, l15, l16, l17, l18, l19, l20]) -class LAngle(Profile): - def __init__(self, name, height, width, tw, tf, r1, r2, ex, ey): - super().__init__(name, "LAngle", "IfcLShapeProfileDef", height, width, tw, tf) +class TProfile(Profile): + def __init__(self, name, height, width, h1:float, b1:float): + super().__init__(name, "T-profile", "Unknown", height, width) # parameters self.type = __class__.__name__ - - self.r1 = r1 # inner fillet - r11 = r1 / sqrt2 - self.r2 = r2 # outer fillet - r21 = r2 / sqrt2 - self.ex = ex # from left - self.ey = ey # from bottom + self.h1 = h1 + self.b1 = b1 # describe points - p1 = Point2D(-ex, -ey) # left bottom - p2 = Point2D(width - ex, -ey) # right bottom - p3 = Point2D(width - ex, -ey + tf - r2) # start arc - p4 = Point2D(width - ex - r2 + r21, -ey + tf - - r2 + r21) # second point arc - p5 = Point2D(width - ex - r2, -ey + tf) # end arc - p6 = Point2D(-ex + tf + r1, -ey + tf) # start arc - p7 = Point2D(-ex + tf + r1 - r11, -ey + tf + - r1 - r11) # second point arc - p8 = Point2D(-ex + tf, -ey + tf + r1) # end arc - p9 = Point2D(-ex + tf, height - ey - r2) # start arc - p10 = Point2D(-ex + tf - r2 + r21, height - ey - - r2 + r21) # second point arc - p11 = Point2D(-ex + tf - r2, height - ey) # end arc - p12 = Point2D(-ex, height - ey) # left top + p1 = Point2D(b1 / 2, -height / 2) # right bottom + p2 = Point2D(b1 / 2, height / 2 - h1) # right middle 1 + p3 = Point2D(width / 2, height / 2 - h1) # right middle 2 + p4 = Point2D(width / 2, height / 2) # right top + p5 = Point2D(-width / 2, height / 2) # left top + p6 = Point2D(-width / 2, height / 2 - h1) # left middle 2 + p7 = Point2D(-b1 / 2, height / 2 - h1) # left middle 1 + p8 = Point2D(-b1 / 2, -height / 2) # left bottom # describe curves l1 = Line2D(p1, p2) l2 = Line2D(p2, p3) - l3 = Arc2D(p3, p4, p5) - l4 = Line2D(p5, p6) - l5 = Arc2D(p6, p7, p8) - l6 = Line2D(p8, p9) - l7 = Arc2D(p9, p10, p11) - l8 = Line2D(p11, p12) - l9 = Line2D(p12, p1) + l3 = Line2D(p3, p4) + l4 = Line2D(p4, p5) + l5 = Line2D(p5, p6) + l6 = Line2D(p6, p7) + l7 = Line2D(p7, p8) + l8 = Line2D(p8, p1) self.curve = PolyCurve2D().by_joined_curves( - [l1, l2, l3, l4, l5, l6, l7, l8, l9]) + [l1, l2, l3, l4, l5, l6, l7, l8]) -class TProfileRounded(Profile): - # ToDo: inner outer fillets in polycurve - def __init__(self, name, height, width, tw, tf, r, r1, r2, ex, ey): - super().__init__(name, "TProfile", "IfcTShapeProfileDef", height, width, tw, tf) +class LProfile(Profile): + def __init__(self, name, height, width, h1:float, b1:float): + super().__init__(name, "L-profile", "Unknown", height, width) - - self.r = r # inner fillet - self.r01 = r / sqrt2 - self.r1 = r1 # outer fillet flange - r11 = r1 / sqrt2 - self.r2 = r2 # outer fillet top web - r21 = r2 / sqrt2 - self.ex = ex # from left - self.ey = ey # from bottom + # parameters + self.type = __class__.__name__ + self.h1 = h1 + self.b1 = b1 # describe points - p1 = Point2D(-ex, -ey) # left bottom - p2 = Point2D(width - ex, -ey) # right bottom - p3 = Point2D(width - ex, -ey + tf - r1) # start arc - p4 = Point2D(width - ex - r1 + r11, -ey + tf - - r1 + r11) # second point arc - p5 = Point2D(width - ex - r1, -ey + tf) # end arc - p6 = Point2D(0.5 * tw + r, -ey + tf) # start arc - p7 = Point2D(0.5 * tw + r - self.r01, -ey + tf + - r - self.r01) # second point arc - p8 = Point2D(0.5 * tw, -ey + tf + r) # end arc - p9 = Point2D(0.5 * tw, -ey + height - r2) # start arc - p10 = Point2D(0.5 * tw - r21, -ey + height - - r2 + r21) # second point arc - p11 = Point2D(0.5 * tw - r2, -ey + height) # end arc - - p12 = Point2D(-p11.x, p11.y) - p13 = Point2D(-p10.x, p10.y) - p14 = Point2D(-p9.x, p9.y) - p15 = Point2D(-p8.x, p8.y) - p16 = Point2D(-p7.x, p7.y) - p17 = Point2D(-p6.x, p6.y) - p18 = Point2D(-p5.x, p5.y) - p19 = Point2D(-p4.x, p4.y) - p20 = Point2D(-p3.x, p3.y) + p1 = Point2D(width / 2, -height / 2) # right bottom + p2 = Point2D(width / 2, -height / 2 + h1) # right middle + p3 = Point2D(-width / 2 + b1, -height / 2 + h1) # middle + p4 = Point2D(-width / 2 + b1, height / 2) # middle top + p5 = Point2D(-width / 2, height / 2) # left top + p6 = Point2D(-width / 2, -height / 2) # left bottom # describe curves l1 = Line2D(p1, p2) - l2 = Line2D(p2, p3) - l3 = Arc2D(p3, p4, p5) - l4 = Line2D(p5, p6) - l5 = Arc2D(p6, p7, p8) - l6 = Line2D(p8, p9) - l7 = Arc2D(p9, p10, p11) - l8 = Line2D(p11, p12) - - l9 = Arc2D(p12, p13, p14) - l10 = Line2D(p14, p15) - l11 = Arc2D(p15, p16, p17) - l12 = Line2D(p17, p18) - l13 = Arc2D(p18, p19, p20) - l14 = Line2D(p20, p1) + l3 = Line2D(p3, p4) + l4 = Line2D(p4, p5) + l5 = Line2D(p5, p6) + l6 = Line2D(p6, p1) - self.curve = PolyCurve2D().by_joined_curves( - [l1, l2, l3, l4, l5, l6, l7, l8, l9, l10, l11, l12, l13, l14]) + self.curve = PolyCurve2D().by_joined_curves([l1, l2, l3, l4, l5, l6]) -class RectangleHollowSection(Profile): - def __init__(self, name, height, width, t, r1, r2): - super().__init__(name, "Rectangle Hollow Section", "IfcRectangleHollowProfileDef", height, width, tw=t, tf=t) +class EProfile(Serializable): + def __init__(self, name, height, width, h1): + super().__init__(name, "E-profile", "Unknown", height, width) # parameters self.type = __class__.__name__ - - self.t = t # thickness - self.r1 = r1 # outer radius - self.r2 = r2 # inner radius - dr = r1 - r1 / sqrt2 - dri = r2 - r2 / sqrt2 - bi = width - t - hi = height - t + self.h1 = h1 # describe points - p1 = Point2D(-width / 2 + r1, - height / 2) # left bottom end arc - p2 = Point2D(width / 2 - r1, - height / 2) # right bottom start arc - p3 = Point2D(width / 2 - dr, - height / 2 + dr) # right bottom mid arc - p4 = Point2D(width / 2, - height / 2 + r1) # right bottom end arc - p5 = Point2D(p4.x, -p4.y) # right start arc - p6 = Point2D(p3.x, -p3.y) # right mid arc - p7 = Point2D(p2.x, -p2.y) # right end arc - p8 = Point2D(-p7.x, p7.y) # left start arc - p9 = Point2D(-p6.x, p6.y) # left mid arc - p10 = Point2D(-p5.x, p5.y) # left end arc - p11 = Point2D(p10.x, -p10.y) # right bottom start arc - p12 = Point2D(p9.x, -p9.y) # right bottom mid arc - - # inner part - p13 = Point2D(-bi / 2 + r2, - hi / 2) # left bottom end arc - p14 = Point2D(bi / 2 - r2, - hi / 2) # right bottom start arc - p15 = Point2D(bi / 2 - dri, - hi / 2 + dri) # right bottom mid arc - p16 = Point2D(bi / 2, - hi / 2 + r2) # right bottom end arc - p17 = Point2D(p16.x, -p16.y) # right start arc - p18 = Point2D(p15.x, -p15.y) # right mid arc - p19 = Point2D(p14.x, -p14.y) # right end arc - p20 = Point2D(-p19.x, p19.y) # left start arc - p21 = Point2D(-p18.x, p18.y) # left mid arc - p22 = Point2D(-p17.x, p17.y) # left end arc - p23 = Point2D(p22.x, -p22.y) # right bottom start arc - p24 = Point2D(p21.x, -p21.y) # right bottom mid arc + p1 = Point2D(width / 2, -height / 2) # right bottom + p2 = Point2D(width / 2, -height / 2 + h1) + p3 = Point2D(-width / 2 + h1, -height / 2 + h1) + p4 = Point2D(-width / 2 + h1, -h1 / 2) + p5 = Point2D(width / 2, -h1 / 2) + p6 = Point2D(width / 2, h1 / 2) + p7 = Point2D(-width / 2 + h1, h1 / 2) + p8 = Point2D(-width / 2 + h1, height / 2 - h1) + p9 = Point2D(width / 2, height / 2 - h1) + p10 = Point2D(width / 2, height / 2) + p11 = Point2D(-width / 2, height / 2) + p12 = Point2D(-width / 2, -height / 2) - # describe outer curves + # describe curves l1 = Line2D(p1, p2) - l2 = Arc2D(p2, p3, p4) - l3 = Line2D(p4, p5) - l4 = Arc2D(p5, p6, p7) - l5 = Line2D(p7, p8) - l6 = Arc2D(p8, p9, p10) - l7 = Line2D(p10, p11) - l8 = Arc2D(p11, p12, p1) - - l9 = Line2D(p1, p13) - # describe inner curves - l10 = Line2D(p13, p14) - l11 = Arc2D(p14, p15, p16) - l12 = Line2D(p16, p17) - l13 = Arc2D(p17, p18, p19) - l14 = Line2D(p19, p20) - l15 = Arc2D(p20, p21, p22) - l16 = Line2D(p22, p23) - l17 = Arc2D(p23, p24, p13) - - l18 = Line2D(p13, p1) + l2 = Line2D(p2, p3) + l3 = Line2D(p3, p4) + l4 = Line2D(p4, p5) + l5 = Line2D(p5, p6) + l6 = Line2D(p6, p7) + l7 = Line2D(p7, p8) + l8 = Line2D(p8, p9) + l9 = Line2D(p9, p10) + l10 = Line2D(p10, p11) + l11 = Line2D(p11, p12) + l12 = Line2D(p12, p1) self.curve = PolyCurve2D().by_joined_curves( - [l1, l2, l3, l4, l5, l6, l7, l8, l9, l10, l11, l12, l13, l14, l15, l16, l17, l18]) + [l1, l2, l3, l4, l5, l6, l7, l8, l9, l10, l11, l12]) -class CProfile(Profile): - def __init__(self, name, width, height, t, r1, ex): - super().__init__(name, "Cold Formed C Profile", "Unknown", height, width, tw=t, tf=t) +class NProfile(Serializable): + def __init__(self, name, height, width, b1): + super().__init__(name, "N-profile", "Unknown", height, width) # parameters self.type = __class__.__name__ - - self.t = t # flange thickness - self.r1 = r1 # outer radius - self.r2 = r1 - t # inner radius - r2 = r1 - t - - self.ex = ex - self.ey = height / 2 - dr = r1 - r1 / sqrt2 - dri = r2 - r2 / sqrt2 - hi = height - t + self.b1 = b1 # describe points - p1 = Point2D(width - ex, -height / 2) # right bottom - p2 = Point2D(r1 - ex, -height / 2) - p3 = Point2D(dr - ex, -height / 2 + dr) - p4 = Point2D(0 - ex, -height / 2 + r1) - p5 = Point2D(p4.x, -p4.y) - p6 = Point2D(p3.x, -p3.y) - p7 = Point2D(p2.x, -p2.y) - p8 = Point2D(p1.x, -p1.y) # right top - p9 = Point2D(width - ex, hi / 2) # right top inner - p10 = Point2D(t + r2 - ex, hi / 2) - p11 = Point2D(t + dri - ex, hi / 2 - dri) - p12 = Point2D(t - ex, hi / 2 - r2) - p13 = Point2D(p12.x, -p12.y) - p14 = Point2D(p11.x, -p11.y) - p15 = Point2D(p10.x, -p10.y) - p16 = Point2D(p9.x, -p9.y) # right bottom inner - # describe outer curves - l1 = Line2D(p1, p2) # bottom - l2 = Arc2D(p2, p3, p4) # right outer fillet - l3 = Line2D(p4, p5) # left outer web - l4 = Arc2D(p5, p6, p7) # left top outer fillet - l5 = Line2D(p7, p8) # outer top - l6 = Line2D(p8, p9) - l7 = Line2D(p9, p10) - l8 = Arc2D(p10, p11, p12) # left top inner fillet - l9 = Line2D(p12, p13) - l10 = Arc2D(p13, p14, p15) # left botom inner fillet - l11 = Line2D(p15, p16) - l12 = Line2D(p16, p1) + p1 = Point2D(width / 2, -height / 2) # right bottom + p2 = Point2D(width / 2, height / 2) + p3 = Point2D(width / 2 - b1, height / 2) + p4 = Point2D(width / 2 - b1, -height / 2 + b1 * 2) + p5 = Point2D(-width / 2 + b1, height / 2) + p6 = Point2D(-width / 2, height / 2) + p7 = Point2D(-width / 2, -height / 2) + p8 = Point2D(-width / 2 + b1, -height / 2) + p9 = Point2D(-width / 2 + b1, height / 2 - b1 * 2) + p10 = Point2D(width / 2 - b1, -height / 2) + + # describe curves + l1 = Line2D(p1, p2) + l2 = Line2D(p2, p3) + l3 = Line2D(p3, p4) + l4 = Line2D(p4, p5) + l5 = Line2D(p5, p6) + l6 = Line2D(p6, p7) + l7 = Line2D(p7, p8) + l8 = Line2D(p8, p9) + l9 = Line2D(p9, p10) + l10 = Line2D(p10, p1) self.curve = PolyCurve2D().by_joined_curves( - [l1, l2, l3, l4, l5, l6, l7, l8, l9, l10, l11, l12]) + [l1, l2, l3, l4, l5, l6, l7, l8, l9, l10]) -class CProfileWithLips(Profile): - def __init__(self, name, width, height, h1, t, r1, ex): - super().__init__(name, "Cold Formed C Profile with Lips", "Unknown", height, width, tw=t, tf=t) +class ArrowProfile(Profile): + def __init__(self, name, length, width, b1, l1): + super().__init__(name, "Arrow-profile", "Unknown", length, width) + # parameters - self.type = __class__.__name__ + self.id = generateID() + self.length = length # length + self.b1 = b1 + self.l1 = l1 - self.h1 = h1 # lip length - self.t = t # flange thickness - self.r1 = r1 # outer radius - self.r2 = r1 - t # inner radius - r2 = r1 - t + # describe points + p1 = Point2D(0, length / 2) # top middle + p2 = Point2D(width / 2, -length / 2 + l1) + # p3 = Point2D(b1 / 2, -length / 2 + l1) + p3 = Point2D(b1 / 2, (-length / 2 + l1) + (length / 2) / 4) + p4 = Point2D(b1 / 2, -length / 2) + p5 = Point2D(-b1 / 2, -length / 2) + # p6 = Point2D(-b1 / 2, -length / 2 + l1) + p6 = Point2D(-b1 / 2, (-length / 2 + l1) + (length / 2) / 4) + p7 = Point2D(-width / 2, -length / 2 + l1) - self.ex = ex - self.ey = height / 2 - dr = r1 - r1 / sqrt2 - dri = r2 - r2 / sqrt2 - hi = height - t + # describe curves + l1 = Line2D(p1, p2) + l2 = Line2D(p2, p3) + l3 = Line2D(p3, p4) + l4 = Line2D(p4, p5) + l5 = Line2D(p5, p6) + l6 = Line2D(p6, p7) + l7 = Line2D(p7, p1) - # describe points - p1 = Point2D(width - ex - r1, -height / 2) # right bottom before fillet - p2 = Point2D(r1 - ex, -height / 2) - p3 = Point2D(dr - ex, -height / 2 + dr) - p4 = Point2D(0 - ex, -height / 2 + r1) - p5 = Point2D(p4.x, -p4.y) - p6 = Point2D(p3.x, -p3.y) - p7 = Point2D(p2.x, -p2.y) - p8 = Point2D(p1.x, -p1.y) # right top before fillet - p9 = Point2D(width - ex - dr, height / 2 - dr) # middle point arc - p10 = Point2D(width - ex, height / 2 - r1) # end fillet - p11 = Point2D(width - ex, height / 2 - h1) - p12 = Point2D(width - ex - t, height / 2 - h1) # bottom lip - p13 = Point2D(width - ex - t, height / 2 - t - r2) # start inner fillet right top - p14 = Point2D(width - ex - t - dri, height / 2 - t - dri) - p15 = Point2D(width - ex - t - r2, height / 2 - t) # end inner fillet right top - p16 = Point2D(0 - ex + t + r2, height / 2 - t) - p17 = Point2D(0 - ex + t + dri, height / 2 - t - dri) - p18 = Point2D(0 - ex + t, height / 2 - t - r2) + self.curve = PolyCurve2D().by_joined_curves( + [l1, l2, l3, l4, l5, l6, l7]) +jsonFile = "https://raw.githubusercontent.com/3BMLabs/Project-Ocondat/master/steelprofile.json" +url = urllib.request.urlopen(jsonFile) +data = json.loads(url.read()) - p19 = Point2D(p18.x, -p18.y) - p20 = Point2D(p17.x, -p17.y) - p21 = Point2D(p16.x, -p16.y) - p22 = Point2D(p15.x, -p15.y) - p23 = Point2D(p14.x, -p14.y) - p24 = Point2D(p13.x, -p13.y) - p25 = Point2D(p12.x, -p12.y) - p26 = Point2D(p11.x, -p11.y) - p27 = Point2D(p10.x, -p10.y) - p28 = Point2D(p9.x, -p9.y) - # describe outer curves - l1 = Line2D(p1, p2) - l2 = Arc2D(p2, p3, p4) - l3 = Line2D(p4, p5) - l4 = Arc2D(p5, p6, p7) # outer fillet right top - l5 = Line2D(p7, p8) - l6 = Arc2D(p8, p9, p10) - l7 = Line2D(p10, p11) - l8 = Line2D(p11, p12) - l9 = Line2D(p12, p13) - l10 = Arc2D(p13, p14, p15) - l11 = Line2D(p15, p16) - l12 = Arc2D(p16, p17, p18) - l13 = Line2D(p18, p19) # inner web - l14 = Arc2D(p19, p20, p21) - l15 = Line2D(p21, p22) - l16 = Arc2D(p22, p23, p24) - l17 = Line2D(p24, p25) - l18 = Line2D(p25, p26) - l19 = Line2D(p26, p27) - l20 = Arc2D(p27, p28, p1) +def is_rectangle_format(shape_name): + match = re.match(r'^(\d{1,4})x(\d{1,4})$', shape_name) + if match: + width, height = int(match.group(1)), int(match.group(2)) + if 0 <= width <= 10000 and 0 <= height <= 10000: + return True, width, height + return False, 0, 0 - self.curve = PolyCurve2D().by_joined_curves( - [l1, l2, l3, l4, l5, l6, l7, l8, l9, l10, l11, l12, l13, l14, l15, l16, l17, l18, l19, l20]) -class LProfileColdFormed(Profile): - def __init__(self, name, width, height, t, r1, ex, ey): - super().__init__(name, "Cold Formed L Profile", "Unknown", height, width, tw=t, tf=t) +class _getProfileDataFromDatabase: + def __init__(self, name): + self.name = name + self.shape_coords = None + self.shape_name = None + self.synonyms = None + for item in data: + for i in item.values(): + synonymList = i[0]["synonyms"] + if self.name.lower() in [synonym.lower() for synonym in synonymList]: + self.shape_coords = i[0]["shape_coords"] + self.shape_name = i[0]["shape_name"] + self.synonyms = i[0]["synonyms"] + if self.shape_coords == None: + check_rect, width, height = is_rectangle_format(name) + if check_rect: + self.shape_coords = [width, height] + self.shape_name = "Rectangle" + self.synonyms = name - # parameters - self.type = __class__.__name__ - self.t = t # flange thickness - self.r1 = r1 # inner radius - self.r2 = r1 - t # outer radius - self.ex = ex - self.ey = ey - r11 = r1 / math.sqrt(2) - r2 = r1 + t - r21 = r2 / math.sqrt(2) +class nameToProfile: + def __init__(self, name1, segmented = True): + profile_data = _getProfileDataFromDatabase(name1) + if profile_data == None: + print(f"profile {name1} not recognised") + profile_name = profile_data.shape_name + if profile_name == None: + profile_data = _getProfileDataFromDatabase(project.structural_fallback_element) + print(f"Error, profile '{name1}' not recognised, define in {jsonFile} | fallback: '{project.structural_fallback_element}'") + profile_name = profile_data.shape_name + self.profile_data = profile_data + self.profile_name = profile_name + name = profile_data.name + self.d1 = profile_data.shape_coords + d1 = self.d1 + if profile_name == "C-channel parallel flange": + prof = CChannelParallelFlange(name,d1[0],d1[1],d1[2],d1[3],d1[4],d1[5]) + elif profile_name == "C-channel sloped flange": + prof = CChannelSlopedFlange(name,d1[0],d1[1],d1[2],d1[3],d1[4],d1[5],d1[6],d1[7],d1[8]) + elif profile_name == "I-shape parallel flange": + prof = IShapeParallelFlange(name,d1[0],d1[1],d1[2],d1[3],d1[4]) + elif profile_name == "I-shape sloped flange": + prof = IShapeParallelFlange(name, d1[0], d1[1], d1[2], d1[3], d1[4]) + #Todo: add sloped flange shape + elif profile_name == "Rectangle": + prof = Rectangle(name,d1[0], d1[1]) + elif profile_name == "Round": + prof = Round(name, d1[1]) + elif profile_name == "Round tube profile": + prof = Roundtube(name, d1[0], d1[1]) + elif profile_name == "LAngle": + prof = LAngle(name,d1[0],d1[1],d1[2],d1[3],d1[4],d1[5],d1[6],d1[7]) + elif profile_name == "TProfile": + prof = TProfileRounded(name, d1[0], d1[1], d1[2], d1[3], d1[4], d1[5], d1[6], d1[7], d1[8]) + elif profile_name == "Rectangle Hollow Section": + prof = RectangleHollowSection(name,d1[0],d1[1],d1[2],d1[3],d1[4]) + self.profile = prof + self.data = d1 + pc2d = self.profile.curve # 2D polycurve + if segmented == True: + pc3d = PolyCurve.by_polycurve_2D(pc2d) + pcsegment = PolyCurve.segment(pc3d, 10) + pc2d2 = pcsegment.to_polycurve_2D() + else: + pc2d2 = pc2d + self.polycurve2d = pc2d2 - # describe points - p1 = Point2D(-ex, -ey + r2) # start arc left bottom - p2 = Point2D(-ex + r2 - r21, -ey + r2 - r21) # second point arc - p3 = Point2D(-ex + r2, -ey) # end arc - p4 = Point2D(width - ex, -ey) # right bottom - p5 = Point2D(width - ex, -ey + t) - p6 = Point2D(-ex + t + r1, -ey + t) # start arc - p7 = Point2D(-ex + t + r1 - r11, -ey + t + - r1 - r11) # second point arc - p8 = Point2D(-ex + t, -ey + t + r1) # end arc - p9 = Point2D(-ex + t, ey) - p10 = Point2D(-ex, ey) # left top +def justifictionToVector(plycrv2D: PolyCurve2D, XJustifiction, Yjustification, ey=None, ez=None): - l1 = Arc2D(p1, p2, p3) - l2 = Line2D(p3, p4) - l3 = Line2D(p4, p5) - l4 = Line2D(p5, p6) - l5 = Arc2D(p6, p7, p8) - l6 = Line2D(p8, p9) - l7 = Line2D(p9, p10) - l8 = Line2D(p10, p1) + # print(XJustifiction) + xval = [] + yval = [] + for i in plycrv2D.curves: + xval.append(i.start.x) + yval.append(i.start.y) - self.curve = PolyCurve2D().by_joined_curves( - [l1, l2, l3, l4, l5, l6, l7, l8]) + #Rect + xmin = min(xval) + xmax = max(xval) + ymin = min(yval) + ymax = max(yval) -class SigmaProfileWithLipsColdFormed(Profile): - def __init__(self, name, width, height, t, r1, h1, h2, h3, b2, ex): - super().__init__(name, "Cold Formed Sigma Profile with Lips", "Unknown", height, width, tw=t, tf=t) + b = xmax-xmin + h = ymax-ymin - # parameters - self.type = __class__.__name__ + # print(b, h) - self.h1 = h1 # LipLength - self.h2 = h2 # MiddleBendLength - self.h3 = h3 # TopBendLength - self.h4 = h4 = (height - h2 - h3 * 2) / 2 - self.h5 = h5 = math.tan(0.5 * math.atan(b2 / h4)) * t - self.b2 = b2 # MiddleBendWidth - self.t = t # flange thickness - self.r1 = r1 # inner radius - self.r2 = r2 = r1 + t # outer radius - self.ex = ex - self.ey = ey = height / 2 - r11 = r11 = r1 / math.sqrt(2) - r21 = r21 = r2 / math.sqrt(2) + dxleft = -xmax + dxright = -xmin + dxcenter = dxleft - 0.5 * b #CHECK + dxorigin = 0 - p1 = Point2D(-ex + b2, -h2 / 2) - p2 = Point2D(-ex, -ey + h3) - p3 = Point2D(-ex, -ey + r2) # start arc left bottom - p4 = Point2D(-ex + r2 - r21, -ey + r2 - r21) # second point arc - p5 = Point2D(-ex + r2, -ey) # end arc - p6 = Point2D(width - ex - r2, -ey) # start arc - p7 = Point2D(width - ex - r2 + r21, -ey + r2 - r21) # second point arc - p8 = Point2D(width - ex, -ey + r2) # end arc - p9 = Point2D(width - ex, -ey + h1) # end lip - p10 = Point2D(width - ex - t, -ey + h1) - p11 = Point2D(width - ex - t, -ey + t + r1) # start arc - p12 = Point2D(width - ex - t - r1 + r11, -ey + - t + r1 - r11) # second point arc - p13 = Point2D(width - ex - t - r1, -ey + t) # end arc - p14 = Point2D(-ex + t + r1, -ey + t) # start arc - p15 = Point2D(-ex + t + r1 - r11, -ey + t + - r1 - r11) # second point arc - p16 = Point2D(-ex + t, -ey + t + r1) # end arc - p17 = Point2D(-ex + t, -ey + h3 - h5) - p18 = Point2D(-ex + b2 + t, -h2 / 2 - h5) - p19 = Point2D(p18.x, -p18.y) - p20 = Point2D(p17.x, -p17.y) - p21 = Point2D(p16.x, -p16.y) - p22 = Point2D(p15.x, -p15.y) - p23 = Point2D(p14.x, -p14.y) - p24 = Point2D(p13.x, -p13.y) - p25 = Point2D(p12.x, -p12.y) - p26 = Point2D(p11.x, -p11.y) - p27 = Point2D(p10.x, -p10.y) - p28 = Point2D(p9.x, -p9.y) - p29 = Point2D(p8.x, -p8.y) - p30 = Point2D(p7.x, -p7.y) - p31 = Point2D(p6.x, -p6.y) - p32 = Point2D(p5.x, -p5.y) - p33 = Point2D(p4.x, -p4.y) - p34 = Point2D(p3.x, -p3.y) - p35 = Point2D(p2.x, -p2.y) - p36 = Point2D(p1.x, -p1.y) + dytop = -ymax + dybottom = -ymin + dycenter = dytop - 0.5 * h #CHECK + dyorigin = 0 - l1 = Line2D(p1, p2) - l2 = Line2D(p2, p3) - l3 = Arc2D(p3, p4, p5) - l4 = Line2D(p5, p6) - l5 = Arc2D(p6, p7, p8) - l6 = Line2D(p8, p9) - l7 = Line2D(p9, p10) - l8 = Line2D(p10, p11) - l9 = Arc2D(p11, p12, p13) - l10 = Line2D(p13, p14) - l11 = Arc2D(p14, p15, p16) - l12 = Line2D(p16, p17) - l13 = Line2D(p17, p18) - l14 = Line2D(p18, p19) - l15 = Line2D(p19, p20) - l16 = Line2D(p20, p21) - l17 = Arc2D(p21, p22, p23) - l18 = Line2D(p23, p24) - l19 = Arc2D(p24, p25, p26) - l20 = Line2D(p26, p27) - l21 = Line2D(p27, p28) - l22 = Line2D(p28, p29) - l23 = Arc2D(p29, p30, p31) - l24 = Line2D(p31, p32) - l25 = Arc2D(p32, p33, p34) - l26 = Line2D(p34, p35) - l27 = Line2D(p35, p36) - l28 = Line2D(p36, p1) + if XJustifiction == "center": + dx = dxorigin #TODO + elif XJustifiction == "left": + dx = dxleft + elif XJustifiction == "right": + dx = dxright + elif XJustifiction == "origin": + dx = dxorigin #TODO + else: + dx = 0 - self.curve = PolyCurve2D().by_joined_curves( - [l1, l2, l3, l4, l5, l6, l7, l8, l9, l10, l11, l12, l13, l14, l15, l16, l17, l18, l19, l20, l21, l22, l23, - l24, l25, - l26, l27, l28]) + if Yjustification == "center": + dy = dyorigin #TODO + elif Yjustification == "top": + dy = dytop + elif Yjustification == "bottom": + dy = dybottom + elif Yjustification == "origin": + dy = dyorigin #TODO + else: + dy = 0 -class ZProfileColdFormed(Profile): - def __init__(self, name, width, height, t, r1): - super().__init__(name, "Cold Formed Z Profile", "Unknown", height, width, tw=t, tf=t) + # print(dx, dy) + v1 = Vector2(dx, dy) + # v1 = Vector2(0, 0) - # parameters - self.type = __class__.__name__ + return v1 +def rgb_to_int(rgb): + r, g, b = [max(0, min(255, c)) for c in rgb] - self.t = t # flange thickness - self.r1 = r1 # inner radius - self.r2 = r2 = r1 + t # outer radius - self.ex = ex = width / 2 - self.ey = ey = height / 2 - r11 = r11 = r1 / math.sqrt(2) - r21 = r21 = r2 / math.sqrt(2) + return (255 << 24) | (r << 16) | (g << 8) | b - p1 = Point2D(-0.5 * t, -ey + t + r1) # start arc - p2 = Point2D(-0.5 * t - r1 + r11, -ey + t + - r1 - r11) # second point arc - p3 = Point2D(-0.5 * t - r1, -ey + t) # end arc - p4 = Point2D(-ex, -ey + t) - p5 = Point2D(-ex, -ey) # left bottom - p6 = Point2D(-r2 + 0.5 * t, -ey) # start arc - p7 = Point2D(-r2 + 0.5 * t + r21, -ey + r2 - r21) # second point arc - p8 = Point2D(0.5 * t, -ey + r2) # end arc - p9 = Point2D(-p1.x, -p1.y) - p10 = Point2D(-p2.x, -p2.y) - p11 = Point2D(-p3.x, -p3.y) - p12 = Point2D(-p4.x, -p4.y) - p13 = Point2D(-p5.x, -p5.y) - p14 = Point2D(-p6.x, -p6.y) - p15 = Point2D(-p7.x, -p7.y) - p16 = Point2D(-p8.x, -p8.y) +class Material: + def __init__(self): + self.name = "none" + self.color = None + self.colorint = None - l1 = Arc2D(p1, p2, p3) - l2 = Line2D(p3, p4) - l3 = Line2D(p4, p5) - l4 = Line2D(p5, p6) - l5 = Arc2D(p6, p7, p8) - l6 = Line2D(p8, p9) - l7 = Arc2D(p9, p10, p11) - l8 = Line2D(p11, p12) - l9 = Line2D(p12, p13) - l10 = Line2D(p13, p14) - l11 = Arc2D(p14, p15, p16) - l12 = Line2D(p16, p1) + @classmethod + def byNameColor(cls, name, color): + M1 = Material() + M1.name = name + M1.color = color + #M1.colorint = rgb_to_int(color) + return M1 - self.curve = PolyCurve2D().by_joined_curves( - [l1, l2, l3, l4, l5, l6, l7, l8, l9, l10, l11, l12]) +#Building Materials +BaseConcrete = Material.byNameColor("Concrete", Color().RGB([192, 192, 192])) +BaseTimber = Material.byNameColor("Timber", Color().RGB([191, 159, 116])) +BaseSteel = Material.byNameColor("Steel", Color().RGB([237, 28, 36])) +BaseOther = Material.byNameColor("Other", Color().RGB([150, 150, 150])) +BaseBrick = Material.byNameColor("Brick", Color().RGB([170, 77, 47])) +BaseBrickYellow = Material.byNameColor("BrickYellow", Color().RGB([208, 187, 147])) -class ZProfileWithLipsColdFormed(Profile): - def __init__(self, name, width, height, t, r1, h1): - super().__init__(name, "Cold Formed Z Profile with Lips", "Unknown", height, width, tw=t, tf=t) +#GIS Materials +BaseBuilding = Material.byNameColor("Building", Color().RGB([150, 28, 36])) +BaseWater = Material.byNameColor("Water", Color().RGB([139, 197, 214])) +BaseGreen = Material.byNameColor("Green", Color().RGB([175, 193, 138])) +BaseInfra = Material.byNameColor("Infra", Color().RGB([234, 234, 234])) +BaseRoads = Material.byNameColor("Infra", Color().RGB([140, 140, 140])) - # parameters - self.type = __class__.__name__ +#class Materialfinish - self.t = t # flange thickness - self.h1 = h1 # lip length - self.r1 = r1 # inner radius - self.r2 = r2 = r1 + t # outer radius - self.ex = ex = width / 2 - self.ey = ey = height / 2 - r11 = r11 = r1 / math.sqrt(2) - r21 = r21 = r2 / math.sqrt(2) +class Node: + """The `Node` class represents a geometric or structural node within a system, defined by a point in space, along with optional attributes like a direction vector, identifying number, and other characteristics.""" + def __init__(self, point=None, vector=None, number=None, distance=0.0, diameter=None, comments=None): + """"Initializes a new Node instance. + + - `id` (str): A unique identifier for the node. + - `type` (str): The class name, "Node". + - `point` (Point, optional): The location of the node in 3D space. + - `vector` (Vector, optional): A vector indicating the orientation or direction associated with the node. + - `number` (any, optional): An identifying number or label for the node. + - `distance` (float): A scalar attribute, potentially representing distance from a reference point or another node. + - `diameter` (any, optional): A diameter associated with the node, useful in structural applications. + - `comments` (str, optional): Additional comments or notes about the node. + """ + self.id = generateID() + self.point = point if isinstance(point, Point) else None + self.vector = vector if isinstance(vector, Vector) else None + self.number = number + self.distance = distance + self.diameter = diameter + self.comments = comments - p1 = Point2D(-0.5 * t, -ey + t + r1) # start arc - p2 = Point2D(-0.5 * t - r1 + r11, -ey + t + r1 - r11) # second point arc - p3 = Point2D(-0.5 * t - r1, -ey + t) # end arc - p4 = Point2D(-ex + t + r1, -ey + t) # start arc - p5 = Point2D(-ex + t + r1 - r11, -ey + t + r1 - r11) # second point arc - p6 = Point2D(-ex + t, -ey + t + r1) # end arc - p7 = Point2D(-ex + t, -ey + h1) - p8 = Point2D(-ex, -ey + h1) - p9 = Point2D(-ex, -ey + r2) # start arc - p10 = Point2D(-ex + r2 - r21, -ey + r2 - r21) # second point arc - p11 = Point2D(-ex + r2, -ey) # end arc - p12 = Point2D(-r2 + 0.5 * t, -ey) # start arc - p13 = Point2D(-r2 + 0.5 * t + r21, -ey + r2 - r21) # second point arc - p14 = Point2D(0.5 * t, -ey + r2) # end arc - p15 = Point2D(-p1.x, -p1.y) - p16 = Point2D(-p2.x, -p2.y) - p17 = Point2D(-p3.x, -p3.y) - p18 = Point2D(-p4.x, -p4.y) - p19 = Point2D(-p5.x, -p5.y) - p20 = Point2D(-p6.x, -p6.y) - p21 = Point2D(-p7.x, -p7.y) - p22 = Point2D(-p8.x, -p8.y) - p23 = Point2D(-p9.x, -p9.y) - p24 = Point2D(-p10.x, -p10.y) - p25 = Point2D(-p11.x, -p11.y) - p26 = Point2D(-p12.x, -p12.y) - p27 = Point2D(-p13.x, -p13.y) - p28 = Point2D(-p14.x, -p14.y) + def serialize(self) -> dict: + """Serializes the node's attributes into a dictionary. - l1 = Arc2D(p1, p2, p3) - l2 = Line2D(p3, p4) - l3 = Arc2D(p4, p5, p6) - l4 = Line2D(p6, p7) - l5 = Line2D(p7, p8) - l6 = Line2D(p8, p9) - l7 = Arc2D(p9, p10, p11) - l8 = Line2D(p11, p12) - l9 = Arc2D(p12, p13, p14) - l10 = Line2D(p14, p15) - l11 = Arc2D(p15, p16, p17) - l12 = Line2D(p17, p18) - l13 = Arc2D(p18, p19, p20) - l14 = Line2D(p20, p21) - l15 = Line2D(p21, p22) - l16 = Line2D(p22, p23) - l17 = Arc2D(p23, p24, p25) - l18 = Line2D(p25, p26) - l19 = Arc2D(p26, p27, p28) - l20 = Line2D(p28, p1) + This method allows for the node's properties to be easily stored or transmitted in a dictionary format. - self.curve = PolyCurve2D().by_joined_curves( - [l1, l2, l3, l4, l5, l6, l7, l8, l9, l10, l11, l12, l13, l14, l15, l16, l17, l18, l19, l20]) + #### Returns: + `dict`: A dictionary containing the serialized attributes of the node. + """ + id_value = str(self.id) if not isinstance( + self.id, (str, int, float)) else self.id + return { + 'id': id_value, + 'type': self.type, + 'point': self.point.serialize() if self.point else None, + 'vector': self.vector.serialize() if self.vector else None, + 'number': self.number, + 'distance': self.distance, + 'diameter': self.diameter, + 'comments': self.comments + } -class TProfile(Profile): - def __init__(self, name, height, width, h1:float, b1:float): - super().__init__(name, "T-profile", "Unknown", height, width) + @staticmethod + def deserialize(data: dict) -> 'Node': + """Recreates a Node object from a dictionary of serialized data. - # parameters - self.type = __class__.__name__ - self.h1 = h1 - self.b1 = b1 + #### Parameters: + - data (dict): The dictionary containing the node's serialized data. - # describe points - p1 = Point2D(b1 / 2, -height / 2) # right bottom - p2 = Point2D(b1 / 2, height / 2 - h1) # right middle 1 - p3 = Point2D(width / 2, height / 2 - h1) # right middle 2 - p4 = Point2D(width / 2, height / 2) # right top - p5 = Point2D(-width / 2, height / 2) # left top - p6 = Point2D(-width / 2, height / 2 - h1) # left middle 2 - p7 = Point2D(-b1 / 2, height / 2 - h1) # left middle 1 - p8 = Point2D(-b1 / 2, -height / 2) # left bottom + #### Returns: + `Node`: A new Node object initialized with the data from the dictionary. + """ + node = Node() + node.id = data.get('id') + node.type = data.get('type') + node.point = Point.deserialize( + data['point']) if data.get('point') else None + node.vector = Vector.deserialize( + data['vector']) if data.get('vector') else None + node.number = data.get('number') + node.distance = data.get('distance') + node.diameter = data.get('diameter') + node.comments = data.get('comments') - # describe curves - l1 = Line2D(p1, p2) - l2 = Line2D(p2, p3) - l3 = Line2D(p3, p4) - l4 = Line2D(p4, p5) - l5 = Line2D(p5, p6) - l6 = Line2D(p6, p7) - l7 = Line2D(p7, p8) - l8 = Line2D(p8, p1) + return node - self.curve = PolyCurve2D().by_joined_curves( - [l1, l2, l3, l4, l5, l6, l7, l8]) + # merge + def merge(self): + """Merges this node with others in a project according to defined rules. -class LProfile(Profile): - def __init__(self, name, height, width, h1:float, b1:float): - super().__init__(name, "L-profile", "Unknown", height, width) + The actual implementation of this method should consider merging nodes based on proximity or other criteria within the project context. + """ + if project.node_merge == True: + pass + else: + pass - # parameters - self.type = __class__.__name__ - self.h1 = h1 - self.b1 = b1 + # snap + def snap(self): + """Adjusts the node's position based on snapping criteria. - # describe points - p1 = Point2D(width / 2, -height / 2) # right bottom - p2 = Point2D(width / 2, -height / 2 + h1) # right middle - p3 = Point2D(-width / 2 + b1, -height / 2 + h1) # middle - p4 = Point2D(-width / 2 + b1, height / 2) # middle top - p5 = Point2D(-width / 2, height / 2) # left top - p6 = Point2D(-width / 2, -height / 2) # left bottom + This could involve aligning the node to a grid, other nodes, or specific geometric entities. + """ + pass - # describe curves - l1 = Line2D(p1, p2) - l2 = Line2D(p2, p3) - l3 = Line2D(p3, p4) - l4 = Line2D(p4, p5) - l5 = Line2D(p5, p6) - l6 = Line2D(p6, p1) + def __str__(self) -> str: + """Generates a string representation of the Node. - self.curve = PolyCurve2D().by_joined_curves([l1, l2, l3, l4, l5, l6]) + #### Returns: + `str`: A string that represents the Node, including its type and potentially other identifying information. + """ -class EProfile(Serializable): - def __init__(self, name, height, width, h1): - super().__init__(name, "E-profile", "Unknown", height, width) + return f"{self.type}" - # parameters - self.type = __class__.__name__ - self.h1 = h1 +def colorlist(extrus, color): + colorlst = [] + for j in range(int(len(extrus.verts) / 3)): + colorlst.append(color) + return (colorlst) - # describe points - p1 = Point2D(width / 2, -height / 2) # right bottom - p2 = Point2D(width / 2, -height / 2 + h1) - p3 = Point2D(-width / 2 + h1, -height / 2 + h1) - p4 = Point2D(-width / 2 + h1, -h1 / 2) - p5 = Point2D(width / 2, -h1 / 2) - p6 = Point2D(width / 2, h1 / 2) - p7 = Point2D(-width / 2 + h1, h1 / 2) - p8 = Point2D(-width / 2 + h1, height / 2 - h1) - p9 = Point2D(width / 2, height / 2 - h1) - p10 = Point2D(width / 2, height / 2) - p11 = Point2D(-width / 2, height / 2) - p12 = Point2D(-width / 2, -height / 2) - # describe curves - l1 = Line2D(p1, p2) - l2 = Line2D(p2, p3) - l3 = Line2D(p3, p4) - l4 = Line2D(p4, p5) - l5 = Line2D(p5, p6) - l6 = Line2D(p6, p7) - l7 = Line2D(p7, p8) - l8 = Line2D(p8, p9) - l9 = Line2D(p9, p10) - l10 = Line2D(p10, p11) - l11 = Line2D(p11, p12) - l12 = Line2D(p12, p1) +# ToDo Na update van color moet ook de colorlist geupdate worden +class Frame(Serializable): + def __init__(self): + self.id = generateID() + self.name = "None" + self.profileName = "None" + self.extrusion = None + self.comments = None + self.structuralType = None + self.start = None + self.end = None + self.curve = None # 2D polycurve of the sectionprofile + self.curve3d = None # Translated 3D polycurve of the sectionprofile + self.length = 0 + self.points = [] + self.coordinateSystem: CoordinateSystem = CSGlobal + self.YJustification = "Origin" # Top, Center, Origin, Bottom + self.ZJustification = "Origin" # Left, Center, Origin, Right + self.YOffset = 0 + self.ZOffset = 0 + self.rotation = 0 + self.material : Material = None + self.color = BaseOther.color + self.profile_data = None #2D polycurve of the sectionprofile (DOUBLE TO BE REMOVED) + self.profile = None #object of 2D profile + self.colorlst = [] + self.vector = None + self.vector_normalised = None + self.centerbottom = None - self.curve = PolyCurve2D().by_joined_curves( - [l1, l2, l3, l4, l5, l6, l7, l8, l9, l10, l11, l12]) + def props(self): + self.vector = Vector(self.end.x-self.start.x, + self.end.y-self.start.y, self.end.z-self.start.z) + self.vector_normalised = Vector.normalize(self.vector) + self.length = Vector.length(self.vector) -class NProfile(Serializable): - def __init__(self, name, height, width, b1): - super().__init__(name, "N-profile", "Unknown", height, width) + @classmethod + def by_startpoint_endpoint(cls, start: Union[Point, Node], end: Union[Point, Node], profile: Union[str, Profile], name: str, material: None, comments=None): + f1 = Frame() + f1.comments = comments - # parameters - self.type = __class__.__name__ - self.b1 = b1 + if start.type == 'Point': + f1.start = start + elif start.type == 'Node': + f1.start = start.point + if end.type == 'Point': + f1.end = end + elif end.type == 'Node': + f1.end = end.point - # describe points - p1 = Point2D(width / 2, -height / 2) # right bottom - p2 = Point2D(width / 2, height / 2) - p3 = Point2D(width / 2 - b1, height / 2) - p4 = Point2D(width / 2 - b1, -height / 2 + b1 * 2) - p5 = Point2D(-width / 2 + b1, height / 2) - p6 = Point2D(-width / 2, height / 2) - p7 = Point2D(-width / 2, -height / 2) - p8 = Point2D(-width / 2 + b1, -height / 2) - p9 = Point2D(-width / 2 + b1, height / 2 - b1 * 2) - p10 = Point2D(width / 2 - b1, -height / 2) + if isinstance(profile,Profile): + f1.curve = profile.curve + f1.profile = profile + elif type(profile).__name__ == "str": + res = nameToProfile(profile) + f1.curve = res.polycurve2d # polycurve2d + f1.points = res.polycurve2d.points + f1.profile = res.profile + else: + print("[by_startpoint_endpoint_profile], input is not correct.") + sys.exit() - # describe curves - l1 = Line2D(p1, p2) - l2 = Line2D(p2, p3) - l3 = Line2D(p3, p4) - l4 = Line2D(p4, p5) - l5 = Line2D(p5, p6) - l6 = Line2D(p6, p7) - l7 = Line2D(p7, p8) - l8 = Line2D(p8, p9) - l9 = Line2D(p9, p10) - l10 = Line2D(p10, p1) + f1.directionVector = Vector.by_two_points(f1.start, f1.end) + f1.length = Vector.length(f1.directionVector) + f1.name = name + f1.extrusion = Extrusion.by_polycurve_height_vector( + f1.curve, f1.length, CSGlobal, f1.start, f1.directionVector) + f1.extrusion.name = name + f1.curve3d = f1.extrusion.polycurve_3d_translated + f1.profileName = profile + f1.material = material + f1.color = material.colorint + f1.colorlst = colorlist(f1.extrusion, f1.color) + f1.props() + return f1 - self.curve = PolyCurve2D().by_joined_curves( - [l1, l2, l3, l4, l5, l6, l7, l8, l9, l10]) + @classmethod + def by_startpoint_endpoint_profile_shapevector(cls, start: Union[Point, Node], end: Union[Point, Node], profile_name: str, name: str, vector2d: Vector2, rotation: float, material: None, comments: None): + f1 = Frame() + f1.comments = comments + if start.type == 'Point': + f1.start = start + elif start.type == 'Node': + f1.start = start.point + if end.type == 'Point': + f1.end = end + elif end.type == 'Node': + f1.end = end.point + + #try: + curv = nameToProfile(profile_name).polycurve2d + #except Exception as e: + # Profile does not exist + #print(f"Profile does not exist: {profile_name}\nError: {e}") -class ArrowProfile(Profile): - def __init__(self, name, length, width, b1, l1): - super().__init__(name, "Arrow-profile", "Unknown", length, width) - - # parameters - self.id = generateID() - self.length = length # length - self.b1 = b1 - self.l1 = l1 + f1.rotation = rotation + curvrot = curv.rotate(rotation) # rotation in degrees + f1.curve = curvrot.translate(vector2d) + f1.XOffset = vector2d.x + f1.YOffset = vector2d.y + f1.directionVector = Vector.by_two_points(f1.start, f1.end) + f1.length = Vector.length(f1.directionVector) + f1.name = name + f1.extrusion = Extrusion.by_polycurve_height_vector( + f1.curve, f1.length, CSGlobal, f1.start, f1.directionVector) + f1.extrusion.name = name + f1.curve3d = f1.extrusion.polycurve_3d_translated + f1.profileName = profile_name + f1.material = material + f1.color = material.colorint + f1.colorlst = colorlist(f1.extrusion, f1.color) + f1.props() + return f1 - # describe points - p1 = Point2D(0, length / 2) # top middle - p2 = Point2D(width / 2, -length / 2 + l1) - # p3 = Point2D(b1 / 2, -length / 2 + l1) - p3 = Point2D(b1 / 2, (-length / 2 + l1) + (length / 2) / 4) - p4 = Point2D(b1 / 2, -length / 2) - p5 = Point2D(-b1 / 2, -length / 2) - # p6 = Point2D(-b1 / 2, -length / 2 + l1) - p6 = Point2D(-b1 / 2, (-length / 2 + l1) + (length / 2) / 4) - p7 = Point2D(-width / 2, -length / 2 + l1) + @classmethod + def by_startpoint_endpoint_profile_justifiction(cls, start: Union[Point, Node], end: Union[Point, Node], profile: Union[str, PolyCurve2D], name: str, XJustifiction: str, YJustifiction: str, rotation: float, material=None, ey: None = float, ez: None = float, structuralType: None = str, comments=None): + f1 = Frame() + f1.comments = comments - # describe curves - l1 = Line2D(p1, p2) - l2 = Line2D(p2, p3) - l3 = Line2D(p3, p4) - l4 = Line2D(p4, p5) - l5 = Line2D(p5, p6) - l6 = Line2D(p6, p7) - l7 = Line2D(p7, p1) + if start.type == 'Point': + f1.start = start + elif start.type == 'Node': + f1.start = start.point + if end.type == 'Point': + f1.end = end + elif end.type == 'Node': + f1.end = end.point - self.curve = PolyCurve2D().by_joined_curves( - [l1, l2, l3, l4, l5, l6, l7]) + f1.structuralType = structuralType + f1.rotation = rotation + + if type(profile).__name__ == "PolyCurve2D": + profile_name = "None" + f1.profile_data = profile + curve = f1.profile_data + elif type(profile).__name__ == "Polygon": + profile_name = "None" + f1.profile_data = PolyCurve2D.by_points(profile.points) + curve = f1.profile_data + elif type(profile).__name__ == "str": + profile_name = profile + f1.profile_data = nameToProfile(profile).polycurve2d # polycurve2d + curve = f1.profile_data + else: + print("[by_startpoint_endpoint_profile], input is not correct.") + sys.exit() + + # curve = f1.profile_data.polycurve2d + + v1 = justifictionToVector(curve, XJustifiction, YJustifiction) # 1 + f1.XOffset = v1.x + f1.YOffset = v1.y + curve = curve.translate(v1) + curve = curve.translate(Vector2(ey, ez)) # 2 + curve = curve.rotate(f1.rotation) # 3 + f1.curve = curve + + f1.directionVector = Vector.by_two_points(f1.start, f1.end) + f1.length = Vector.length(f1.directionVector) + f1.name = name + f1.extrusion = Extrusion.by_polycurve_height_vector( + f1.curve, f1.length, CSGlobal, f1.start, f1.directionVector) + f1.extrusion.name = name + f1.curve3d = f1.extrusion.polycurve_3d_translated + + try: + pnew = PolyCurve.by_joined_curves(f1.curve3d.curves) + f1.centerbottom = PolyCurve.centroid(pnew) + except: + pass + + f1.profileName = profile_name + f1.material = material + f1.color = material.colorint + f1.colorlst = colorlist(f1.extrusion, f1.color) + f1.props() + return f1 + + @classmethod + def by_startpoint_endpoint_rect(cls, start: Union[Point, Node], end: Union[Point, Node], width: float, height: float, name: str, rotation: float, material=None, comments=None): + # 2D polycurve + f1 = Frame() + f1.comments = comments + + if start.type == 'Point': + f1.start = start + elif start.type == 'Node': + f1.start = start.point + if end.type == 'Point': + f1.end = end + elif end.type == 'Node': + f1.end = end.point + + f1.directionVector = Vector.by_two_points(f1.start, f1.end) + f1.length = Vector.length(f1.directionVector) + f1.name = name + + prof = Rectangle(str(width)+"x"+str(height),width,height) + polycurve = prof.curve + f1.profile = prof + curvrot = polycurve.rotate(rotation) + f1.extrusion = Extrusion.by_polycurve_height_vector( + curvrot, f1.length, CSGlobal, f1.start, f1.directionVector) + f1.extrusion.name = name + f1.curve3d = curvrot + f1.profileName = name + f1.material = material + f1.color = material.colorint + f1.colorlst = colorlist(f1.extrusion, f1.color) + f1.props() + return f1 + + + @classmethod + def by_point_height_rotation(cls, start: Union[Point, Node], height: float, polycurve: PolyCurve2D, frame_name: str, rotation: float, material=None, comments=None): + # 2D polycurve + f1 = Frame() + f1.comments = comments + + if start.type == 'Point': + f1.start = start + elif start.type == 'Node': + f1.start = start.point + + f1.end = Point.translate(f1.start, Vector(0, 0.00001, height)) + + # self.curve = Line(start, end) + f1.directionVector = Vector.by_two_points(f1.start, f1.end) + f1.length = Vector.length(f1.directionVector) + f1.name = frame_name + f1.profileName = frame_name + curvrot = polycurve.rotate(rotation) # rotation in degrees + f1.extrusion = Extrusion.by_polycurve_height_vector( + curvrot, f1.length, CSGlobal, f1.start, f1.directionVector) + f1.extrusion.name = frame_name + f1.curve3d = curvrot + f1.material = material + f1.color = material.colorint + f1.colorlst = colorlist(f1.extrusion, f1.color) + f1.props() + return f1 + + @classmethod + def by_point_profile_height_rotation(cls, start: Union[Point, Node], height: float, profile_name: str, rotation: float, material=None, comments=None): + f1 = Frame() + f1.comments = comments + + if start.type == 'Point': + f1.start = start + elif start.type == 'Node': + f1.start = start.point + # TODO vertical column not possible + f1.end = Point.translate(f1.start, Vector(0, height)) + + # self.curve = Line(start, end) + f1.directionVector = Vector.by_two_points(f1.start, f1.end) + f1.length = Vector.length(f1.directionVector) + f1.name = profile_name + f1.profileName = profile_name + curv = nameToProfile(profile_name).polycurve2d + curvrot = curv.rotate(rotation) # rotation in degrees + f1.extrusion = Extrusion.by_polycurve_height_vector( + curvrot.curves, f1.length, CSGlobal, f1.start, f1.directionVector) + f1.extrusion.name = profile_name + f1.curve3d = curvrot + f1.profileName = profile_name + f1.material = material + f1.color = material.colorint + f1.colorlst = colorlist(f1.extrusion, f1.color) + f1.props() + return f1 + + @classmethod + def by_startpoint_endpoint_curve_justifiction(cls, start: Union[Point, Node], end: Union[Point, Node], polycurve: PolyCurve2D, name: str, XJustifiction: str, YJustifiction: str, rotation: float, material=None, comments=None): + f1 = Frame() + f1.comments = comments + + if start.type == 'Point': + f1.start = start + elif start.type == 'Node': + f1.start = start.point + if end.type == 'Point': + f1.end = end + elif end.type == 'Node': + f1.end = end.point + + f1.rotation = rotation + curv = polycurve + curvrot = curv.rotate(rotation) # rotation in degrees + # center, left, right, origin / center, top bottom, origin + v1 = justifictionToVector(curvrot, XJustifiction, YJustifiction) + f1.XOffset = v1.x + f1.YOffset = v1.y + f1.curve = curv.translate(v1) + f1.directionVector = Vector.by_two_points(f1.start, f1.end) + f1.length = Vector.length(f1.directionVector) + f1.name = name + f1.extrusion = Extrusion.by_polycurve_height_vector( + f1.curve.curves, f1.length, CSGlobal, f1.start, f1.directionVector) + f1.extrusion.name = name + f1.profileName = "none" + f1.material = material + f1.color = material.colorint + f1.colorlst = colorlist(f1.extrusion, f1.color) + f1.props() + return f1 + + def write(self, project): + project.objects.append(self) + return self class Interval: @@ -8640,1660 +8667,15007 @@ def __inner_mother_surface(self): self.mother_surface_origin_point] ) - def __inner_frames(self): - """Creates inner frame objects based on division distances within the rectangle system. - Utilizes the division distances to place vertical frames across the inner width of the rectangle system. These frames are represented both as Frame objects within the system and as symbolic lines for visualization. + def __inner_frames(self): + """Creates inner frame objects based on division distances within the rectangle system. + Utilizes the division distances to place vertical frames across the inner width of the rectangle system. These frames are represented both as Frame objects within the system and as symbolic lines for visualization. + + #### Effects: + - Generates Frame objects for each division, placing them vertically within the rectangle system. + - Populates `inner_frame_objects` with these Frame instances. + - Adds symbolic representations of these frames to `symbolic_inner_grids`. + """ + for i in self.division_system.distances: + start_point = Point.translate( + self.mother_surface_origin_point_x_zero, Vector(i, 0, 0)) + end_point = Point.translate( + self.mother_surface_origin_point_x_zero, Vector(i, self.inner_height, 0)) + self.inner_frame_objects.append( + Frame.by_start_point_endpoint_curve_justifiction( + start_point, end_point, self.inner_frame_type.curve, "innerframe", "center", "top", 0, self.material) + ) + self.symbolic_inner_grids.append( + Line(start=start_point, end=end_point)) + + def __outer_frames(self): + """Generates the outer frame objects for the rectangle system. + Creates Frame objects for the bottom, top, left, and right boundaries of the rectangle system. Each frame is defined by its start and end points, along with its type and material. Symbolic lines representing these frames are also generated for visualization. + + #### Effects: + - Creates Frame instances for the outer boundaries of the rectangle system and adds them to `outer_frame_objects`. + - Generates symbolic Line instances for each outer frame and adds them to `symbolic_outer_grids`. + """ + bottomframe = Frame.by_start_point_endpoint_curve_justifiction(Point(0, 0, 0), Point( + self.width, 0, 0), self.bottom_frame_type.curve, "bottomframe", "left", "top", 0, self.material) + self.symbolic_outer_grids.append( + Line(start=Point(0, 0, 0), end=Point(self.width, 0, 0))) + + topframe = Frame.by_start_point_endpoint_curve_justifiction(Point(0, self.height, 0), Point( + self.width, self.height, 0), self.top_frame_type.curve, "bottomframe", "right", "top", 0, self.material) + self.symbolic_outer_grids.append( + Line(start=Point(0, self.height, 0), end=Point(self.width, self.height, 0))) + + leftframe = Frame.by_start_point_endpoint_curve_justifiction(Point(0, self.bottom_frame_type.b, 0), Point( + 0, self.height-self.top_frame_type.b, 0), self.left_frame_type.curve, "leftframe", "right", "top", 0, self.material) + self.symbolic_outer_grids.append(Line(start=Point( + 0, self.bottom_frame_type.b, 0), end=Point(0, self.height-self.top_frame_type.b, 0))) + + rightframe = Frame.by_start_point_endpoint_curve_justifiction(Point(self.width, self.bottom_frame_type.b, 0), Point( + self.width, self.height-self.top_frame_type.b, 0), self.right_frame_type.curve, "leftframe", "left", "top", 0, self.material) + self.symbolic_outer_grids.append(Line(start=Point(self.width, self.bottom_frame_type.b, 0), end=Point( + self.width, self.height-self.top_frame_type.b, 0))) + + self.outer_frame_objects.append(bottomframe) + self.outer_frame_objects.append(topframe) + self.outer_frame_objects.append(leftframe) + self.outer_frame_objects.append(rightframe) + + def by_width_height_divisionsystem_studtype(self, width: float, height: float, frame_width: float, frame_height: float, division_system: DivisionSystem, filling: bool) -> 'RectangleSystem': + """Configures the rectangle system with specified dimensions, division system, and frame types. + This method sets the dimensions of the rectangle system, configures the frame types based on the provided dimensions, and applies a division system to generate inner frames. Optionally, it can also fill the system with panels based on the inner divisions. + + #### Parameters: + - `width` (float): The width of the rectangle system. + - `height` (float): The height of the rectangle system. + - `frame_width` (float): The width of the frame elements. + - `frame_height` (float): The height (thickness) of the frame elements. + - `division_system` (DivisionSystem): The division system to apply for inner divisions. + - `filling` (bool): A flag indicating whether to fill the divided areas with panels. + + #### Returns: + `RectangleSystem`: The instance itself, updated with the new configuration. + + #### Example usage: + ```python + rectangle_system = RectangleSystem() + rectangle_system.by_width_height_divisionsystem_studtype(2000, 3000, 38, 184, divisionSystem, True) + ``` + """ + self.width = width + self.height = height + self.bottom_frame_type = Rectangle( + "bottom_frame_type", frame_width, frame_height) + self.top_frame_type = Rectangle( + "top_frame_type", frame_width, frame_height) + self.left_frame_type = Rectangle( + "left_frame_type", frame_width, frame_height) + self.right_frame_type = Rectangle( + "left_frame_type", frame_width, frame_height) + self.inner_frame_type = Rectangle( + "inner_frame_type", frame_width, frame_height) + self.division_system = division_system + self.__inner_mother_surface() + self.__inner_frames() + self.__outer_frames() + if filling: + self.__inner_panels() + else: + pass + return self + + +class pattern_system: + """The `pattern_system` class is designed to define and manipulate patterns for architectural or design applications. It is capable of generating various patterns based on predefined or dynamically generated parameters.""" + def __init__(self): + """Initializes a new pattern_system instance.""" + self.name = None + self.id = generateID() + self.pattern = None + self.basepanels = [] # contains a list with basepanels of the system + # contains a list sublists with Vector which represent the repetition of the system + self.vectors = [] + + def stretcher_bond_with_joint(self, name: str, brick_width: float, brick_length: float, brick_height: float, joint_width: float, joint_height: float): + """Configures a stretcher bond pattern with joints for the pattern_system. + Establishes the fundamental vectors and base panels for a stretcher bond, taking into account brick dimensions and joint sizes. This pattern alternates bricks in each row, offsetting them by half a brick length. + + #### Parameters: + - `name` (str): Name of the pattern configuration. + - `brick_width` (float): Width of the brick. + - `brick_length` (float): Length of the brick. + - `brick_height` (float): Height of the brick. + - `joint_width` (float): Width of the joint between bricks. + - `joint_height` (float): Height of the joint between brick layers. + + #### Returns: + The instance itself, updated with the stretcher bond pattern configuration. + + #### Example usage: + ```python + + ``` + """ + self.name = name + # Vectors of panel 1 + V1 = Vector(0, (brick_height + joint_height)*2, 0) # dy + V2 = Vector(brick_length+joint_width, 0, 0) # dx + self.vectors.append([V1, V2]) + + # Vectors of panel 2 + V3 = Vector(0, (brick_height + joint_height) * 2, 0) # dy + V4 = Vector(brick_length + joint_width, 0, 0) # dx + self.vectors.append([V3, V4]) + + dx = (brick_length+joint_width)/2 + dy = brick_height+joint_height + + PC1 = PolyCurve().by_points([Point(0, 0, 0), Point(0, brick_height, 0), Point( + brick_length, brick_height, 0), Point(brick_length, 0, 0), Point(0, 0, 0)]) + PC2 = PolyCurve().by_points([Point(dx, dy, 0), Point(dx, brick_height+dy, 0), Point( + brick_length+dx, brick_height+dy, 0), Point(brick_length+dx, dy, 0), Point(dx, dy, 0)]) + BasePanel1 = Panel.by_polycurve_thickness( + PC1, brick_width, 0, "BasePanel1", BaseBrick.colorint) + BasePanel2 = Panel.by_polycurve_thickness( + PC2, brick_width, 0, "BasePanel2", BaseBrick.colorint) + + self.basepanels.append(BasePanel1) + self.basepanels.append(BasePanel2) + return self + + def tile_bond_with_joint(self, name: str, tile_width: float, tile_height: float, tile_thickness: float, joint_width: float, joint_height: float): + """Configures a tile bond pattern with specified dimensions and joint sizes for the pattern_system. + Defines a simple tiling pattern where tiles are laid out in rows and columns, separated by specified joint widths and heights. This method sets up base panels to represent individual tiles and their arrangement vectors. + + #### Parameters: + - `name` (str): The name of the tile bond pattern configuration. + - `tile_width` (float): The width of a single tile. + - `tile_height` (float): The height of a single tile. + - `tile_thickness` (float): The thickness of the tile. + - `joint_width` (float): The width of the joint between adjacent tiles. + - `joint_height` (float): The height of the joint between tile rows. + + #### Returns: + The instance itself, updated with the tile bond pattern configuration. + + #### Example Usage: + ```python + pattern_system = pattern_system() + pattern_system.tile_bond_with_joint('TilePattern', 200, 300, 10, 5, 5) + ``` + This configures the `pattern_system` with a tile bond pattern named 'TilePattern', where each tile measures 200x300x10 units, with 5 units of spacing between tiles. + """ + self.name = name + # Vectors of panel 1 + V1 = Vector(0, (tile_height + joint_height), 0) # dy + V2 = Vector(tile_width+joint_width, 0, 0) # dx + self.vectors.append([V1, V2]) + + PC1 = PolyCurve().by_points([Point(0, 0, 0), Point(0, tile_height, 0), Point( + tile_width, tile_height, 0), Point(tile_width, 0, 0)]) + BasePanel1 = Panel.by_polycurve_thickness( + PC1, tile_thickness, 0, "BasePanel1", BaseBrick.colorint) + + self.basepanels.append(BasePanel1) + return self + + def cross_bond_with_joint(self, name: str, brick_width: float, brick_length: float, brick_height: float, joint_width: float, joint_height: float): + """Configures a cross bond pattern with joints for the pattern_system. + Sets up a complex brick laying pattern combining stretcher (lengthwise) and header (widthwise) bricks in alternating rows, creating a cross bond appearance. This method defines the base panels and their positioning vectors to achieve the cross bond pattern. + + #### Parameters: + - `name` (str): The name of the cross bond pattern configuration. + - `brick_width` (float): The width of a single brick. + - `brick_length` (float): The length of the brick. + - `brick_height` (float): The height of the brick layer. + - `joint_width` (float): The width of the joint between bricks. + - `joint_height` (float): The height of the joint between brick layers. + + #### Returns: + The instance itself, updated with the cross bond pattern configuration. + + #### Example Usage: + ```python + pattern_system = pattern_system() + pattern_system.cross_bond_with_joint('CrossBondPattern', 90, 190, 80, 10, 10) + ``` + In this configuration, `pattern_system` is set to a cross bond pattern named 'CrossBondPattern', with bricks measuring 90x190x80 units and 10 units of joint spacing in both directions. + """ + self.name = name + lagenmaat = brick_height + joint_height + # Vectors of panel 1 (strek) + V1 = Vector(0, (brick_height + joint_height) * 4, 0) # dy spacing + V2 = Vector(brick_length + joint_width, 0, 0) # dx spacing + self.vectors.append([V1, V2]) + + # Vectors of panel 2 (koppen 1) + V3 = Vector(0, (brick_height + joint_height) * 2, 0) # dy spacing + V4 = Vector(brick_length + joint_width, 0, 0) # dx spacing + self.vectors.append([V3, V4]) + + dx2 = (brick_width + joint_width)/2 # start x offset + dy2 = lagenmaat # start y offset + + # Vectors of panel 3 (strekken) + V5 = Vector(0, (brick_height + joint_height) * 4, 0) # dy spacing + V6 = Vector(brick_length + joint_width, 0, 0) # dx spacing + self.vectors.append([V5, V6]) + + dx3 = (brick_length + joint_width)/2 # start x offset + dy3 = lagenmaat * 2 # start y offset + + # Vectors of panel 4 (koppen 2) + V7 = Vector(0, (brick_height + joint_height) * 2, 0) # dy spacing + V8 = Vector(brick_length + joint_width, 0, 0) # dx spacing + self.vectors.append([V7, V8]) + + dx4 = (brick_width + joint_width)/2 + \ + (brick_width + joint_width) # start x offset + dy4 = lagenmaat # start y offset + + PC1 = PolyCurve().by_points([Point(0, 0, 0), Point(0, brick_height, 0), Point( + brick_length, brick_height, 0), Point(brick_length, 0, 0), Point(0, 0, 0)]) + PC2 = PolyCurve().by_points([Point(dx2, dy2, 0), Point(dx2, brick_height+dy2, 0), Point( + brick_width+dx2, brick_height+dy2, 0), Point(brick_width+dx2, dy2, 0), Point(dx2, dy2, 0)]) + PC3 = PolyCurve().by_points([Point(dx3, dy3, 0), Point(dx3, brick_height+dy3, 0), Point( + brick_length+dx3, brick_height+dy3, 0), Point(brick_length+dx3, dy3, 0), Point(dx3, dy3, 0)]) + PC4 = PolyCurve().by_points([Point(dx4, dy4, 0), Point(dx4, brick_height+dy4, 0), Point( + brick_width+dx4, brick_height+dy4, 0), Point(brick_width+dx4, dy4, 0), Point(dx4, dy4, 0)]) + + BasePanel1 = Panel.by_polycurve_thickness( + PC1, brick_width, 0, "BasePanel1", BaseBrick.colorint) + BasePanel2 = Panel.by_polycurve_thickness( + PC2, brick_width, 0, "BasePanel2", BaseBrick.colorint) + BasePanel3 = Panel.by_polycurve_thickness( + PC3, brick_width, 0, "BasePanel3", BaseBrick.colorint) + BasePanel4 = Panel.by_polycurve_thickness( + PC4, brick_width, 0, "BasePanel4", BaseBrickYellow.colorint) + + self.basepanels.append(BasePanel1) + self.basepanels.append(BasePanel2) + self.basepanels.append(BasePanel3) + self.basepanels.append(BasePanel4) + + return self + + +def pattern_geom(pattern_system, width: float, height: float, start_point: Point = None) -> list[Panel]: + """Generates a geometric pattern based on a pattern_system within a specified area. + Takes a pattern_system and fills a defined width and height area starting from an optional start point with the pattern described by the system. + + #### Parameters: + - `pattern_system`: The pattern_system instance defining the pattern. + - `width` (float): Width of the area to fill with the pattern. + - `height` (float): Height of the area to fill with the pattern. + - `start_point` (Point, optional): Starting point for the pattern generation. + + #### Returns: + `list[Panel]`: A list of Panel instances constituting the generated pattern. + + #### Example usage: + ```python + + ``` + """ + start_point = start_point or Point(0, 0, 0) + test = pattern_system + panels = [] + + for i, j in zip(test.basepanels, test.vectors): + ny = int(height / (j[0].y)) # number of panels in y-direction + nx = int(width / (j[1].x)) # number of panels in x-direction + PC = i.origincurve + thickness = i.thickness + color = i.colorint + + # YX ARRAY + yvectdisplacement = j[0] + yvector = Point.to_vector(start_point) + xvectdisplacement = j[1] + xvector = Vector(0, 0, 0) + + ylst = [] + for k in range(ny): + yvector = yvectdisplacement + yvector + for l in range(nx): + # Copy in x-direction + xvector = xvectdisplacement + xvector + xyvector = yvector + xvector + # translate curve in x and y-direction + PCNew = PolyCurve.copy_translate(PC, xyvector) + pan = Panel.by_polycurve_thickness( + PCNew, thickness, 0, "name", color) + panels.append(pan) + xvector = Vector.sum( + xvectdisplacement, Vector(-test.basepanels[0].origincurve.curves[1].length, 0, 0)) + return panels + + +def fillin(perimeter: PolyCurve2D, pattern: pattern_geom) -> pattern_system: + """Fills in a given perimeter with a specified pattern. + Uses a bounding box to define the perimeter within which a pattern is applied, based on a geometric pattern generation function. + + #### Parameters: + - `perimeter` (PolyCurve2D): The 2D perimeter to fill in. + - `pattern` (function): The pattern generation function to apply within the perimeter. + + #### Returns: + `pattern_system`: A pattern_system object that represents the filled-in area. + + #### Example usage: + ```python + + ``` + """ + + bb = Rect().by_points(perimeter.points) + + for pt in bb.corners: + project.objects.append(pt) + bb_perimeter = PolyCurve.by_points(bb.corners) + + return [bb_perimeter] + +class Matrix(Serializable, list[list]): + """ + elements are ordered like [row][column] or [y][x] + """ + def __init__(self, matrix:list[list]=[[1, 0], [0, 1]]) -> 'Matrix': + list.__init__(self, matrix) + + + @property + def cols(self) -> 'int': + """returns the width (x size) of this matrix in columns.""" + return len(self[0]) + + @property + def rows(self) -> 'int': + """returns the height (y size) of this matrix in columns.""" + return len(self) + + @staticmethod + def scale(dimensions: int, scalar: float)-> 'Matrix': + + match dimensions: + case 1: + arr = [[scalar]] + case 2: + arr = [[scalar,0], + [0,scalar]] + case 3: + arr = [[scalar, 0, 0], + [0, scalar, 0], + [0, 0, scalar]] + case 4: + arr= [[scalar, 0, 0, 0], + [0, scalar, 0, 0], + [0, 0, scalar, 0], + [0, 0, 0, scalar]] + return Matrix(arr) + + @staticmethod + def empty(rows:int, cols = None): + """creates a matrix of size n x m (rows x columns or y * x or h * w)""" + if cols == None: + cols = rows + return Matrix([[0 for col in range(cols)] for row in range(rows)]) + + @staticmethod + def identity(dimensions:int): + return Matrix.scale(dimensions, 1) + + @staticmethod + def translate(toAdd: Vector): + dimensions:int = len(toAdd) + 1 + return Matrix([[1 if x == y else toAdd[y] if x == len(toAdd) else 0 for x in range(dimensions)] for y in range(len(toAdd))]) + + def __mul__(self, other:Self | Coords | Line | Rect | PointList): + """CAUTION! MATRICES NEED TO MULTIPLY FROM RIGHT TO LEFT! + for example: translate * rotate (rotate first, translate after) + and: matrix * point (point first, multiplied by matrix after)""" + if isinstance(other, Matrix): + #multiply matrices with eachother + #https://www.geeksforgeeks.org/multiplication-two-matrices-single-line-using-numpy-python/ + + #visualisation of resulting sizes: + #https://en.wikipedia.org/wiki/Matrix_multiplication + + #the number of columns (width) in the first matrix needs to be equal to the number of rows (height) in the second matrix + #(look at for i in range(other.height)) + + #we are multiplying row vectors of self with col vectors of other + if self.cols == other.rows: + resultRows = self.rows + resultCols = other.cols + result:Matrix = Matrix.empty(resultRows, resultCols) + # explicit for loops + for row in range(self.rows): + for col in range(other.cols): + for multiplyIndex in range(other.rows): + #this is the simple code, which would work if the number of self.cols was equal to other.rows + result[row][col] += self[row][multiplyIndex] * other[multiplyIndex][col] + else: + resultCols = max(self.cols, other.cols) + resultRows = max(self.rows, other.rows) + + result:Matrix = Matrix.empty(resultRows, resultCols) + + #the size of the vector that we're multiplying. + multiplyVectorSize = max(self.cols, other.rows) + + # explicit for loops + for row in range(resultRows): + for col in range(resultCols): + for multiplyIndex in range(multiplyVectorSize): + #if an element doesn't exist in the matrix, we use an identity element. + selfValue = self[row][multiplyIndex] if row < self.rows and multiplyIndex < self.cols else 1 if multiplyIndex == row else 0 + otherValue = other[multiplyIndex][col] if col < other.cols and multiplyIndex < other.rows else 1 if multiplyIndex == col else 0 + result[row][col] += selfValue * otherValue + + elif isinstance(other, PointList): + return other.__class__([self * p for p in other]) + #point comes in from top and comes out to the right: + # | + # v + #a b + #c d -> + elif isinstance(other, Coords): + result: Coords = Coords([0] * self.rows) + #loop over column vectors and multiply them with the vector. sum the results (multiplied col 1 + multiplied col 2) to get the final product! + for col in range(self.cols): + if col < len(other): + for row in range(self.rows): + result[row] += self[row][col] * other[col] + else: + #otherValue = 1, just add the vector + for row in range(self.rows): + result[row] += self[row][col] + return result + elif isinstance(other, Line): + return Line(self * other.start, self * other.end) + elif isinstance(other, Rect): + mp0 = self * other.p0 + mp1 = self * other.p1 + return Rect.by_points([mp0, mp1]) + return result + + transform = multiply = __mul__ + + def add(self, other: 'Matrix'): + if self.shape() != other.shape(): + raise ValueError("Matrices must have the same dimensions") + return Matrix([[self[i][j] + other.matrix[i][j] for j in range(len(self[0]))] for i in range(len(self))]) + + def all(self, axis=None): + if axis is None: + return all(all(row) for row in self) + elif axis == 0: + return [all(self[row][col] for row in range(len(self))) for col in range(len(self[0]))] + elif axis == 1: + return [all(col) for col in self] + else: + raise ValueError("Axis must be None, 0, or 1") + + def any(self, axis=None): + if axis is None: + return any(any(row) for row in self) + elif axis == 0: + return [any(self[row][col] for row in range(len(self))) for col in range(len(self[0]))] + elif axis == 1: + return [any(col) for col in self] + else: + raise ValueError("Axis must be None, 0, or 1") + + def argmax(self, axis=None): + if axis is None: + flat_list = [item for sublist in self for item in sublist] + return flat_list.index(max(flat_list)) + elif axis == 0: + return [max(range(len(self)), key=lambda row: self[row][col]) for col in range(len(self[0]))] + elif axis == 1: + return [max(range(len(row)), key=lambda col: row[col]) for row in self] + else: + raise ValueError("Axis must be None, 0, or 1") + + def argmin(self, axis=None): + if axis is None: + flat_list = [item for sublist in self for item in sublist] + return flat_list.index(min(flat_list)) + elif axis == 0: + return [min(range(len(self)), key=lambda row: self[row][col]) for col in range(len(self[0]))] + elif axis == 1: + return [min(range(len(row)), key=lambda col: row[col]) for row in self] + else: + raise ValueError("Axis must be None, 0, or 1") + + def argpartition(self, kth, axis=0): + def partition(arr, kth): + pivot = arr[kth] + less = [i for i in range(len(arr)) if arr[i] < pivot] + equal = [i for i in range(len(arr)) if arr[i] == pivot] + greater = [i for i in range(len(arr)) if arr[i] > pivot] + return less + equal + greater + + if axis == 0: + return [partition([self[row][col] for row in range(len(self))], kth) for col in range(len(self[0]))] + elif axis == 1: + return [partition(row, kth) for row in self] + + def argsort(self, axis=0): + if axis == 0: + return [[row for row, val in sorted(enumerate(col), key=lambda x: x[1])] for col in zip(*self)] + elif axis == 1: + return [list(range(len(self[0]))) for _ in self] + + def astype(self, dtype): + cast_matrix = [[dtype(item) for item in row] for row in self] + return Matrix(cast_matrix) + + def byteswap(self, inplace=False): + if inplace: + for i in range(len(self)): + for j in range(len(self[i])): + self[i][j] = ~self[i][j] + return self + else: + new_matrix = [[~item for item in row] for row in self] + return Matrix(new_matrix) + + def choose(self, choices, mode='raise'): + if mode != 'raise': + raise NotImplementedError("Only 'raise' mode is implemented") + + chosen = [[choices[item] for item in row] for row in self] + return Matrix(chosen) + + def compress(self, condition, axis=None): + if axis == 0: + compressed = [row for row, cond in zip( + self, condition) if cond] + return Matrix(compressed) + else: + raise NotImplementedError("Axis other than 0 is not implemented") + + def clip(self, min=None, max=None): + clipped_matrix = [] + for row in self: + clipped_row = [max if max is not None and val > + max else min if min is not None and val < min else val for val in row] + clipped_matrix.append(clipped_row) + return Matrix(clipped_matrix) + + def conj(self): + conjugated_matrix = [[complex(item).conjugate() + for item in row] for row in self] + return Matrix(conjugated_matrix) + + def conjugate(self): + return self.conj() + + def copy(self): + copied_matrix = copy.deepcopy(self) + return Matrix(copied_matrix) + + def cumprod(self, axis=None): + if axis is None: + flat_list = self.flatten() + cumprod_list = [] + cumprod = 1 + for item in flat_list: + cumprod *= item + cumprod_list.append(cumprod) + return Matrix([cumprod_list]) + else: + raise NotImplementedError( + "Axis handling not implemented in this example") + + def cumsum(self, axis=None): + if axis is None: + flat_list = self.flatten() + cumsum_list = [] + cumsum = 0 + for item in flat_list: + cumsum += item + cumsum_list.append(cumsum) + return Matrix([cumsum_list]) + else: + raise NotImplementedError( + "Axis handling not implemented in this example") + + def diagonal(self, offset=0): + return [self[i][i + offset] for i in range(len(self)) if 0 <= i + offset < len(self[i])] + + def dump(self, file): + with open(file, 'wb') as f: + pickle.dump(self, f) + + def dumps(self): + return pickle.dumps(self) + + def fill(self, value): + for i in range(len(self)): + for j in range(len(self[i])): + self[i][j] = value + + @staticmethod + def from_points(from_point: Point, to_point: Point): + Vz = Vector.by_two_points(from_point, to_point) + Vz = Vector.normalize(Vz) + Vzglob = Vector(0, 0, 1) + Vx = Vector.cross_product(Vz, Vzglob) + if Vector.length(Vx) == 0: + Vx = Vector(1, 0, 0) if Vz.x != 1 else Vector(0, 1, 0) + Vx = Vector.normalize(Vx) + Vy = Vector.cross_product(Vx, Vz) + + return Matrix([ + [Vx.x, Vy.x, Vz.x, from_point.x], + [Vx.y, Vy.y, Vz.y, from_point.y], + [Vx.z, Vy.z, Vz.z, from_point.z], + [0, 0, 0, 1] + ]) + + def flatten(self): + return [item for sublist in self for item in sublist] + + def getA(self): + return self + + def getA1(self): + return [item for sublist in self for item in sublist] + + def getH(self): + conjugate_transposed = [[complex(self[j][i]).conjugate() for j in range( + len(self))] for i in range(len(self[0]))] + return Matrix(conjugate_transposed) + + def getI(self): + raise NotImplementedError( + "Matrix inversion is a complex operation not covered in this simple implementation.") + + def getT(self): + return self.transpose() + + def getfield(self, dtype, offset=0): + raise NotImplementedError( + "This method is conceptual and depends on structured data support within the Matrix.") + + def item(self, *args): + if len(args) == 1: + index = args[0] + rows, cols = len(self), len(self[0]) + return self[index // cols][index % cols] + elif len(args) == 2: + return self[args[0]][args[1]] + else: + raise ValueError("Invalid number of indices.") + + def itemset(self, *args): + if len(args) == 2: + index, value = args + rows, cols = len(self), len(self[0]) + self[index // cols][index % cols] = value + elif len(args) == 3: + row, col, value = args + self[row][col] = value + else: + raise ValueError("Invalid number of arguments.") + + def max(self, axis=None): + if axis is None: + return max(item for sublist in self for item in sublist) + elif axis == 0: + return [max(self[row][col] for row in range(len(self))) for col in range(len(self[0]))] + elif axis == 1: + return [max(row) for row in self] + else: + raise ValueError("Invalid axis.") + + def mean(self, axis=None): + if axis is None: + flat_list = self.flatten() + return sum(flat_list) / len(flat_list) + elif axis == 0: + return [sum(self[row][col] for row in range(len(self))) / len(self) for col in range(len(self[0]))] + elif axis == 1: + return [sum(row) / len(row) for row in self] + else: + raise ValueError("Axis must be None, 0, or 1") + + def min(self, axis=None): + if axis is None: + return min(item for sublist in self for item in sublist) + elif axis == 0: + return [min(self[row][col] for row in range(len(self))) for col in range(len(self[0]))] + elif axis == 1: + return [min(row) for row in self] + else: + raise ValueError("Invalid axis.") + + @staticmethod + def zeros(rows, cols): + return Matrix([[0 for _ in range(cols)] for _ in range(rows)]) + + @staticmethod + def participation(self): + pass + + def prod(self, axis=None): + if axis is None: + return reduce(lambda x, y: x * y, [item for sublist in self for item in sublist], 1) + elif axis == 0: + return [reduce(lambda x, y: x * y, [self[row][col] for row in range(len(self))], 1) for col in range(len(self[0]))] + elif axis == 1: + return [reduce(lambda x, y: x * y, row, 1) for row in self] + else: + raise ValueError("Invalid axis.") + + def ptp(self, axis=None): + if axis is None: + flat_list = [item for sublist in self for item in sublist] + return max(flat_list) - min(flat_list) + elif axis == 0: + return [max([self[row][col] for row in range(len(self))]) - min([self[row][col] for row in range(len(self))]) for col in range(len(self[0]))] + elif axis == 1: + return [max(row) - min(row) for row in self] + else: + raise ValueError("Invalid axis.") + + def put(self, indices, values): + if len(indices) != len(values): + raise ValueError("Length of indices and values must match.") + flat_list = self.ravel() + for index, value in zip(indices, values): + flat_list[index] = value + + @staticmethod + def random(rows, cols): + import random + return Matrix([[random.random() for _ in range(cols)] for _ in range(rows)]) + + def ravel(self): + return [item for sublist in self for item in sublist] + + def repeat(self, repeats, axis=None): + if axis is None: + flat_list = self.ravel() + repeated = [item for item in flat_list for _ in range(repeats)] + return Matrix([repeated]) + elif axis == 0: + repeated_matrix = [ + row for row in self for _ in range(repeats)] + elif axis == 1: + repeated_matrix = [ + [item for item in row for _ in range(repeats)] for row in self] + else: + raise ValueError("Invalid axis.") + return Matrix(repeated_matrix) + + def reshape(self, rows, cols): + flat_list = self.flatten() + if len(flat_list) != rows * cols: + raise ValueError( + "The total size of the new array must be unchanged.") + reshaped = [flat_list[i * cols:(i + 1) * cols] for i in range(rows)] + return Matrix(reshaped) + + def resize(self, new_shape): + new_rows, new_cols = new_shape + current_rows, current_cols = len(self), len( + self[0]) if self else 0 + if new_rows < current_rows: + self = self[:new_rows] + else: + for _ in range(new_rows - current_rows): + self.append([0] * current_cols) + for row in self: + if new_cols < current_cols: + row[:] = row[:new_cols] + else: + row.extend([0] * (new_cols - current_cols)) + + def round(self, decimals=0): + rounded_matrix = [[round(item, decimals) + for item in row] for row in self] + return Matrix(rounded_matrix) + + def searchsorted(self, v, side='left'): + flat_list = self.flatten() + i = 0 + if side == 'left': + while i < len(flat_list) and flat_list[i] < v: + i += 1 + elif side == 'right': + while i < len(flat_list) and flat_list[i] <= v: + i += 1 + else: + raise ValueError("side must be 'left' or 'right'") + return i + + def setfield(self, val, dtype, offset=0): + raise NotImplementedError( + "Structured data operations are not supported in this Matrix class.") + + def setflags(self, write=None, align=None, uic=None): + print("This Matrix class does not support setting flags directly.") + + def shape(self): + return len(self), len(self[0]) + + def sort(self, axis=-1): + if axis == -1 or axis == 1: + for row in self: + row.sort() + elif axis == 0: + transposed = [[self[j][i] for j in range( + len(self))] for i in range(len(self[0]))] + for row in transposed: + row.sort() + self = [[transposed[j][i] for j in range( + len(transposed))] for i in range(len(transposed[0]))] + else: + raise ValueError("Axis out of range.") + + def squeeze(self): + squeezed_matrix = [row for row in self if any(row)] + return Matrix(squeezed_matrix) + + def std(self, axis=None, ddof=0): + var = self.var(axis=axis, ddof=ddof) + if isinstance(var, list): + return [x ** 0.5 for x in var] + else: + return var ** 0.5 + + def subtract(self, other): + if self.shape() != other.shape(): + raise ValueError("Matrices must have the same dimensions") + return Matrix([[self[i][j] - other.matrix[i][j] for j in range(len(self[0]))] for i in range(len(self))]) + + def sum(self, axis=None): + if axis is None: + return sum(sum(row) for row in self) + elif axis == 0: + return [sum(self[row][col] for row in range(len(self))) for col in range(len(self[0]))] + elif axis == 1: + return [sum(row) for row in self] + else: + raise ValueError("Axis must be None, 0, or 1") + + def swapaxes(self, axis1, axis2): + if axis1 == 0 and axis2 == 1 or axis1 == 1 and axis2 == 0: + return Matrix([[self[j][i] for j in range(len(self))] for i in range(len(self[0]))]) + else: + raise ValueError("Axis values out of range for a 2D matrix.") + + def take(self, indices, axis=None): + if axis is None: + flat_list = [item for sublist in self for item in sublist] + return Matrix([flat_list[i] for i in indices]) + elif axis == 0: + return Matrix([self[i] for i in indices]) + else: + raise ValueError( + "Axis not supported or out of range for a 2D matrix.") + + def tobytes(self): + byte_array = bytearray() + for row in self: + for item in row: + byte_array.extend(struct.pack('i', item)) + return bytes(byte_array) + + def tofile(self, fid, sep="", format="%s"): + if isinstance(fid, str): + with open(fid, 'wb' if sep == "" else 'w') as f: + self._write_to_file(f, sep, format) + else: + self._write_to_file(fid, sep, format) + + def _write_to_file(self, file, sep, format): + if sep == "": + file.write(self.tobytes()) + else: + for row in self: + line = sep.join(format % item for item in row) + "\n" + file.write(line) + + def __str__(self): + # '\n'.join([str(row) for row in self]) + #vs code doesn't work with new lines + return 'Matrix(' + list.__str__(self) + ')' + + def trace(self, offset=0): + rows, cols = len(self), len(self[0]) + return sum(self[i][i + offset] for i in range(min(rows, cols - offset)) if 0 <= i + offset < cols) + + def transpose(self): + transposed = [[self[j][i] for j in range( + len(self))] for i in range(len(self[0]))] + return Matrix(transposed) + + def var(self, axis=None, ddof=0): + if axis is None: + flat_list = self.flatten() + mean = sum(flat_list) / len(flat_list) + return sum((x - mean) ** 2 for x in flat_list) / (len(flat_list) - ddof) + elif axis == 0 or axis == 1: + means = self.mean(axis=axis) + if axis == 0: + return [sum((self[row][col] - means[col]) ** 2 for row in range(len(self))) / (len(self) - ddof) for col in range(len(self[0]))] + else: + return [sum((row[col] - means[idx]) ** 2 for col in range(len(row))) / (len(row) - ddof) for idx, row in enumerate(self)] + else: + raise ValueError("Axis must be None, 0, or 1") + + def _validate(self): + rows = len(self) + cols = len(self[0]) if rows > 0 else 0 + return rows, cols + +class Intersect: + def __init__(self): + pass + + def get_line_intersect(line1: Line, line2: Line) -> Point: + p1, p2 = line1.start, line1.end + p1X, p1Y, P1Z = p1.x, p1.y, p1.z + p2X, p2Y, P2Z = p2.x, p2.y, p2.z + + p3, p4 = line2.start, line2.end + p3X, p3Y, P3Z = p3.x, p3.y, p3.z + p4X, p4Y, P4Z = p4.x, p4.y, p4.z + + print(p1X, p1Y, P1Z) + + +class Text: + """The `Text` class is designed to represent and manipulate text within a coordinate system, allowing for the creation of text objects with specific fonts, sizes, and positions. It is capable of generating and translating text into a series of geometric representations.""" + def __init__(self, text: str = None, font_family: 'str' = None, cs='CoordinateSystem', height=None) -> "Text": + """Initializes a new Text instance + + - `id` (str): A unique identifier for the text object. + - `type` (str): The class name, "Text". + - `text` (str, optional): The text string to be represented. + - `font_family` (str, optional): The font family of the text, defaulting to "Arial". + - `xyz` (Vector): The origin point of the text in the coordinate system. + - `csglobal` (CoordinateSystem): The global coordinate system applied to the text. + - `x`, `y`, `z` (float): The position offsets for the text within its coordinate system. + - `scale` (float, optional): The scale factor applied to the text size. + - `height` (float, optional): The height of the text characters. + - `bbHeight` (float, optional): The bounding box height of the text. + - `width` (float, optional): The calculated width of the text string. + - `character_offset` (int): The offset between characters. + - `space` (int): The space between words. + - `curves` (list): A list of curves representing the text geometry. + - `points` (list): A list of points derived from the text geometry. + - `path_list` (list): A list containing the path data for each character. + """ + self.id = generateID() + self.text = text + self.font_family = font_family or "arial" + self.xyz = cs.Origin + self.csglobal = cs + self.x, self.y, self.z = 0, 0, 0 + self.scale = None + self.height = height or project.font_height + self.bbHeight = None + self.width = None + self.character_offset = 150 + self.space = 850 + self.curves = [] + self.points = [] + self.path_list = self.load_path() + self.load_o_example = self.load_o() + + def serialize(self) -> 'dict': + """Serializes the text object's attributes into a dictionary. + This method is useful for exporting the text object's properties, making it easier to save or transmit as JSON. + + #### Returns: + dict: A dictionary containing the serialized attributes of the text object. + + #### Example usage: + ```python + + ``` + """ + id_value = str(self.id) if not isinstance( + self.id, (str, int, float)) else self.id + return { + 'id': id_value, + 'type': self.type, + 'text': self.text, + 'font_family': self.font_family, + 'xyz': self.xyz, + 'csglobal': self.csglobal.serialize(), + 'x': self.x, + 'y': self.y, + 'z': self.z, + 'scale': self.scale, + 'height': self.height, + 'bbHeight': self.bbHeight, + 'width': self.width, + 'character_offset': self.character_offset, + 'space': self.space, + 'curves': [curve.serialize() for curve in self.curves], + 'points': self.points, + 'path_list': self.path_list, + } + + def load_path(self) -> 'str': + """Loads the glyph paths for the specified text from a JSON file. + This method fetches the glyph paths for each character in the text attribute, using a predefined font JSON file. + + #### Returns: + str: A string representation of the glyph paths for the text. + + #### Example usage: + ```python + + ``` + """ + with open('library/text/json/Calibri.json', 'r', encoding='utf-8') as file: + response = file.read() + glyph_data = json.loads(response) + output = [] + for letter in self.text: + if letter in glyph_data: + output.append(glyph_data[letter]["glyph-path"]) + elif letter == " ": + output.append("space") + return output + + def load_o(self) -> 'str': + """Loads the glyph paths for the specified text from a JSON file. + This method fetches the glyph paths for each character in the text attribute, using a predefined font JSON file. + + #### Returns: + str: A string representation of the glyph paths for the text. + + #### Example usage: + ```python + + ``` + """ + with open('library/text/json/Calibri.json', 'r', encoding='utf-8') as file: + response = file.read() + glyph_data = json.loads(response) + load_o = [] + letter = "o" + if letter in glyph_data: + load_o.append(glyph_data[letter]["glyph-path"]) + return load_o + + def write(self) -> 'List[List[PolyCurve]]': + """Generates a list of PolyCurve objects representing the text. + Transforms the text into geometric representations based on the specified font, scale, and position. + + #### Returns: + List[List[PolyCurve]]: A list of lists containing PolyCurve objects representing the text geometry. + + #### Example usage: + ```python + + ``` + """ + # start ref_symbol + path = self.load_o_example + ref_points = [] + ref_allPoints = [] + for segment in path: + pathx = parse_path(segment) + for segment in pathx: + segment_type = segment.__class__.__name__ + if segment_type == 'Line': + ref_points.extend( + [(segment.start.real, segment.start.imag), (segment.end.real, segment.end.imag)]) + ref_allPoints.extend( + [(segment.start.real, segment.start.imag), (segment.end.real, segment.end.imag)]) + elif segment_type == 'CubicBezier': + ref_points.extend(segment.sample(10)) + ref_allPoints.extend(segment.sample(10)) + elif segment_type == 'QuadraticBezier': + for i in range(11): + t = i / 10.0 + point = segment.point(t) + ref_points.append((point.real, point.imag)) + ref_allPoints.append((point.real, point.imag)) + elif segment_type == 'Arc': + ref_points.extend(segment.sample(10)) + ref_allPoints.extend(segment.sample(10)) + height = self.calculate_bounding_box(ref_allPoints)[2] + self.scale = self.height / height + # end ref_symbol + + output_list = [] + for letter_path in self.path_list: + points = [] + allPoints = [] + if letter_path == "space": + self.x += self.space + self.character_offset + pass + else: + path = parse_path(letter_path) + for segment in path: + segment_type = segment.__class__.__name__ + if segment_type == 'Move': + if len(points) > 0: + points = [] + allPoints.append("M") + subpath_started = True + elif subpath_started: + if segment_type == 'Line': + points.extend( + [(segment.start.real, segment.start.imag), (segment.end.real, segment.end.imag)]) + allPoints.extend( + [(segment.start.real, segment.start.imag), (segment.end.real, segment.end.imag)]) + elif segment_type == 'CubicBezier': + points.extend(segment.sample(10)) + allPoints.extend(segment.sample(10)) + elif segment_type == 'QuadraticBezier': + for i in range(11): + t = i / 10.0 + point = segment.point(t) + points.append((point.real, point.imag)) + allPoints.append((point.real, point.imag)) + elif segment_type == 'Arc': + points.extend(segment.sample(10)) + allPoints.extend(segment.sample(10)) + if points: + output_list.append( + self.convert_points_to_polyline(allPoints)) + width = self.calculate_bounding_box(allPoints)[1] + self.x += width + self.character_offset + + height = self.calculate_bounding_box(allPoints)[2] + self.bbHeight = height + pList = [] + for ply in flatten(output_list): + translated = self.translate(ply) + pList.append(translated) + + for pl in pList: + for pt in pl.points: + self.points.append(pt) + + # print(f'Object text naar objects gestuurd.') + return pList + + def translate(self, polyCurve: 'PolyCurve') -> 'PolyCurve': + """Translates a PolyCurve according to the text object's global coordinate system and scale. + + #### Parameters: + polyCurve (PolyCurve): The PolyCurve to be translated. + + #### Returns: + PolyCurve: The translated PolyCurve. + + #### Example usage: + ```python + + ``` + """ + trans = [] + for pt in polyCurve.points: + pscale = Point.product(self.scale, pt) + pNew = transform_point_2(pscale, self.csglobal) + trans.append(pNew) + return polyCurve.by_points(trans) + + def calculate_bounding_box(self, points: 'list[Point]') -> tuple: + """Calculates the bounding box for a given set of points. + + #### Parameters: + points (list): A list of points to calculate the bounding box for. + + #### Returns: + tuple: A tuple containing the bounding box, its width, and its height. + + #### Example usage: + ```python + + ``` + """ + + points = [elem for elem in points if elem != 'M'] + ptList = [Point2D(pt[0], pt[1]) for pt in points] + bounding_box_polyline = Rect().by_points(ptList) + return bounding_box_polyline, bounding_box_polyline.width, bounding_box_polyline.length + + def convert_points_to_polyline(self, points: 'list[Point]') -> 'PolyCurve': + """Converts a list of points into a PolyCurve. + This method is used to generate a PolyCurve from a series of points, typically derived from text path data. + + #### Parameters: + points (list): A list of points to be converted into a PolyCurve. + + #### Returns: + PolyCurve: A PolyCurve object representing the points. + + #### Example usage: + ```python + + ``` + """ + output_list = [] + sub_lists = [[]] + tempPoints = [elem for elem in points if elem != 'M'] + x_values = [point[0] for point in tempPoints] + y_values = [point[1] for point in tempPoints] + + xmin = min(x_values) + ymin = min(y_values) + + for item in points: + if item == 'M': + sub_lists.append([]) + else: + x = item[0] + self.x - xmin + y = item[1] + self.y - ymin + z = self.xyz.z + eput = x, y, z + sub_lists[-1].append(eput) + output_list = [[Point(point[0], point[1], self.xyz.z) + for point in element] for element in sub_lists] + + polyline_list = [ + PolyCurve.by_points( + [Point(coord.x, coord.y, self.xyz.z) for coord in pts]) + for pts in output_list + ] + return polyline_list + + +class Geometry: + def Translate(object, v): + if object.type == 'Point': + p1 = Point.to_matrix(object) + v1 = Vector.to_matrix(v) + + ar1 = Point.to_matrix(p1) + ar2 = Vector.to_matrix(v1) + + c = [ar1[i] + ar2[i] for i in range(len(ar1))] + + return Point(c[0], c[1], c[2]) + + elif object.type == 'Line': + return Line(Geometry.Translate(object.start, v), (Geometry.Translate(object.end, v))) + + elif object.type == "PolyCurve": + translated_points = [] + + # Extract the direction components from the Vector object + direction_x, direction_y, direction_z = v.x, v.y, v.z + + for point in object.points: + p1 = Point.to_matrix(point) + # Apply the translation + c = [p1[0] + direction_x, p1[1] + + direction_y, p1[2] + direction_z] + + translated_points.append(Point(c[0], c[1], c[2])) + + return PolyCurve.by_points(translated_points) + else: + print(f"[translate] '{object.type}' object is not added yet") + +# from project.fileformat import project + + +class Point: + """Represents a point in 3D space with x, y, and z coordinates.""" + def __init__(self, x: float, y: float, z: float) -> 'Point': + """Initializes a new Point instance with the given x, y, and z coordinates. + + - `x` (float): X-coordinate of the point. + - `y` (float): Y-coordinate of the point. + - `z` (float): Z-coordinate of the point. + """ + self.id = generateID() + self.type = __class__.__name__ + self.x: float = 0.0 + self.y: float = 0.0 + self.z: float = 0.0 + self.x = float(x) + self.y = float(y) + self.z = float(z) + self.value = self.x, self.y, self.z + self.units = "mm" + + def __str__(self) -> str: + """Converts the point to its string representation.""" + return f"{__class__.__name__}(X = {self.x:.3f}, Y = {self.y:.3f}, Z = {self.z:.3f})" + + def serialize(self): + """Serializes the point object.""" + id_value = str(self.id) if not isinstance( + self.id, (str, int, float)) else self.id + return { + 'id': id_value, + 'type': self.type, + 'x': self.x, + 'y': self.y, + 'z': self.z, + 'value': self.value, + 'units': self.units + } + + @staticmethod + def deserialize(data): + """Deserializes the point object from the provided data.""" + return Point(data['x'], data['y'], data['z']) + + @staticmethod + def distance(point_1: 'Point', point_2: 'Point') -> float: + """Computes the Euclidean distance between two 3D points. + + #### Parameters: + - `point_1` (Point): The first point. + - `point_2` (Point): The second point. + + #### Returns: + `float`: The Euclidean distance between `point_1` and `point_2`. + + #### Example usage: + ```python + point_1 = Point(100.23, 182, 19) + point_2 = Point(81, 0.1, -901) + output = Point.distance(point_1, point_2) + # 938.0071443757771 + ``` + """ + + return math.sqrt((point_1.x - point_2.x)**2 + (point_1.y - point_2.y)**2 + (point_1.z - point_2.z)**2) + + @staticmethod + def distance_list(points: list['Point']) -> float: + """Calculates distances between points in a list. + + #### Parameters: + - `points` (list): List of points. + + #### Returns: + `float`: Total distance calculated between all the points in the list. + + #### Example usage: + ```python + point_1 = Point(231, 13, 76) + point_2 = Point(71, 12.3, -232) + point_3 = Point(2, 71, -102) + output = Point.distance_list([point_1, point_2, point_3]) + # [(, , 158.45090722365714), (, , 295.78539517697624), (, , 347.07994756251765)] + ``` + """ + distances = [] + for i in range(len(points)): + for j in range(i+1, len(points)): + distances.append( + (points[i], points[j], Point.distance(points[i], points[j]))) + distances.sort(key=lambda x: x[2]) + return distances + + @staticmethod + def difference(point_1: 'Point', point_2: 'Point'): + """Computes the difference between two points as a Vector3 object. + + #### Parameters: + - `point_1` (Point): First point. + - `point_2` (Point): Second point. + + #### Returns: + `Vector3`: Difference between the two input points as a Vector3 object. + + #### Example usage: + ```python + point_1 = Point(23, 1, 23) + point_2 = Point(93, 0, -19) + output = Point.difference(point_1, point_2) + # Vector3(X = 70.000, Y = -1.000, Z = -42.000) + ``` + """ + from abstract.vector import Vector3 + return Vector3( + point_2.x - point_1.x, + point_2.y - point_1.y, + point_2.z - point_1.z + ) + + @staticmethod + def translate(point: 'Point', vector) -> 'Point': + """Translates the point by a given vector. + + #### Parameters: + - `point` (Point): The point to be translated. + - `vector` (Vector3): The translation vector. + + #### Returns: + `Point`: Translated point. + + #### Example usage: + ```python + point = Point(23, 1, 23) + vector = Vector3(93, 0, -19) + output = Point.translate(point, vector) + # Point(X = 116.000, Y = 1.000, Z = 4.000) + ``` + """ + from abstract.vector import Vector3 + + ar1 = Point.to_matrix(point) + ar2 = Vector3.to_matrix(vector) + if len(ar1) == len(ar2): + c = [ar1[i] + ar2[i] for i in range(len(ar1))] + else: + c = [0, 0, 0] + raise ValueError("Arrays must have the same size") + return Point(c[0], c[1], c[2]) + + @staticmethod + def origin(point_1: 'Point', point_2: 'Point') -> 'Point': + """Computes the midpoint between two points. + + #### Parameters: + - `point_1` (Point): First point. + - `point_2` (Point): Second point. + + #### Returns: + `Point`: Midpoint between the two input points. + + #### Example usage: + ```python + point_1 = Point(100.23, 182, 19) + point_2 = Point(81, 0.1, -901) + output = Point.origin(point_1, point_2) + # Point(X = 90.615, Y = 91.050, Z = -441.000) + ``` + """ + return Point( + (point_1.x + point_2.x) / 2, + (point_1.y + point_2.y) / 2, + (point_1.z + point_2.z) / 2 + ) + + @staticmethod + def point_2D_to_3D(point_2D) -> 'Point': + """Converts a 2D point to a 3D point with zero z-coordinate. + + #### Parameters: + - `point2D` (Point): 2D point to be converted. + + #### Returns: + `Point`: 3D point with zero z-coordinate. + + #### Example usage: + ```python + point_1 = Point2D(19, 30) + output = Point.point_2D_to_3D(point_1) + # Point(X = 19.000, Y = 30.000, Z = 0.000) + ``` + """ + return Point( + point_2D.x, + point_2D.y, + 0 + ) + + @staticmethod + def to_vector(point: 'Point'): + """Converts the point to a Vector3 object. + + #### Parameters: + - `point` (Point): Point to be converted. + + #### Returns: + `Vector3`: Vector representation of the point. + + #### Example usage: + ```python + point_1 = Point(9, 20, 10) + output = Point.to_vector(point_1) + # Vector3(X = 9.000, Y = 20.000, Z = 10.000) + ``` + """ + from abstract.vector import Vector3 + return Vector3( + point.x, + point.y, + point.z + ) + + @staticmethod + def sum(point_1: 'Point', point_2: 'Point') -> 'Point': + """Computes the sum of two points. + + #### Parameters: + - `point_1` (Point): First point. + - `point_2` (Point): Second point. + + #### Returns: + `Point`: Sum of the two input points. + + #### Example usage: + ```python + point_1 = Point(23, 1, 23) + point_2 = Point(93, 0, -19) + output = Point.sum(point_1, point_2) + # Point(X = 116.000, Y = 1.000, Z = 4.000) + ``` + """ + return Point( + point_1.x + point_2.x, + point_1.y + point_2.y, + point_1.z + point_2.z + ) + + @staticmethod + def diff(point_1: 'Point', point_2: 'Point') -> 'Point': + """Computes the difference between two points. + + #### Parameters: + - `point_1` (Point): First point. + - `point_2` (Point): Second point. + + #### Returns: + `Point`: Difference between the two input points. + + #### Example usage: + ```python + point_1 = Point(100.23, 182, 19) + point_2 = Point(81, 0.1, -901) + output = Point.diff(point_1, point_2) + # Point(X = 19.230, Y = 181.900, Z = 920.000) + ``` + """ + return Point( + point_1.x - point_2.x, + point_1.y - point_2.y, + point_1.z - point_2.z + ) + + @staticmethod + def rotate_XY(point: 'Point', beta: float, dz: float) -> 'Point': + """Rotates the point about the Z-axis by a given angle. + + #### Parameters: + - `point` (Point): Point to be rotated. + - `beta` (float): Angle of rotation in degrees. + - `dz` (float): Offset in the z-coordinate. + + #### Returns: + `Point`: Rotated point. + + #### Example usage: + ```python + point_1 = Point(19, 30, 12.3) + output = Point.rotate_XY(point_1, 90, 12) + # Point(X = -30.000, Y = 19.000, Z = 24.300) + ``` + """ + return Point( + math.cos(math.radians(beta))*point.x - + math.sin(math.radians(beta))*point.y, + math.sin(math.radians(beta))*point.x + + math.cos(math.radians(beta))*point.y, + point.z + dz + ) + + @staticmethod + def product(number: float, point: 'Point') -> 'Point': + """Scales the point by a given factor. + + #### Parameters: + - `number` (float): Scaling factor. + - `point` (Point): Point to be scaled. + + #### Returns: + `Point`: Scaled point. + + #### Example usage: + ```python + point_1 = Point(9, 20, 10) + output = Point.product(12, point_1) + # Point(X = 108.000, Y = 240.000, Z = 120.000) + ``` + """ + return Point( + point.x*number, + point.y*number, + point.z*number + ) + + @staticmethod + def intersect(point_1: 'Point', point_2: 'Point') -> 'Point': + """Checks if two points intersect. + + #### Parameters: + - `point_1` (Point): First point. + - `point_2` (Point): Second point. + + #### Returns: + `boolean`: True if points intersect, False otherwise. + + #### Example usage: + ```python + point_1 = Point(23, 1, 23) + point_2 = Point(93, 0, -19) + output = Point.intersect(point_1, point_2) + # False + ``` + """ + if point_1.x == point_2.x and point_1.y == point_2.y and point_1.z == point_2.z: + return True + else: + return False + + @staticmethod + def to_matrix(point: 'Point') -> 'Point': + """Converts the point to a list. + + #### Parameters: + Converts the point to a list. + + #### Returns: + `list`: List representation of the point. + + #### Example usage: + ```python + point_1 = Point(23, 1, 23) + output = Point.to_matrix(point_1) + # [23.0, 1.0, 23.0] + ``` + """ + return [point.x, point.y, point.z] + + @staticmethod + def from_matrix(list: list) -> 'Point': + """Converts a list to a Point object. + + #### Parameters: + Converts a list to a Point object. + + #### Returns: + `Point`: Point object created from the list. + + #### Example usage: + ```python + point_1 = [19, 30, 12.3] + output = Point.from_matrix(point_1) + # Point(X = 19.000, Y = 30.000, Z = 12.300) + ``` + """ + return Point( + list[0], + list[1], + list[2] + ) + + +class CoordinateSystem: + """Represents a coordinate system in 3D space defined by an origin point and normalized x, y, and z axis vectors.""" + def __init__(self, origin: Point, x_axis, y_axis, z_axis) -> 'CoordinateSystem': + """Initializes a new CoordinateSystem instance with the given origin and axis vectors. + The axis vectors are normalized to ensure they each have a length of 1, providing a standard basis for the coordinate system. + + - `origin` (Point): The origin point of the coordinate system. + - `x_axis` (Vector3): The initial vector representing the X-axis before normalization. + - `y_axis` (Vector3): The initial vector representing the Y-axis before normalization. + - `z_axis` (Vector3): The initial vector representing the Z-axis before normalization. + """ + from abstract.vector import Vector3 + self.id = generateID() + self.type = __class__.__name__ + self.Origin = origin + self.Xaxis = Vector3.normalize(x_axis) + self.Y_axis = Vector3.normalize(y_axis) + self.Z_axis = Vector3.normalize(z_axis) + + @classmethod + def by_origin(coordinate_system, origin: Point) -> 'CoordinateSystem': + """Creates a CoordinateSystem with a specified origin. + + #### Parameters: + - `origin` (`Point`): The origin point of the new coordinate system. + + #### Returns: + `CoordinateSystem`: A new CoordinateSystem object with the specified origin. + + #### Example usage: + ```python + + ``` + """ + from abstract.coordinatesystem import X_axis, Y_Axis, Z_Axis + return coordinate_system(origin, x_axis=X_axis, y_axis=Y_Axis, z_axis=Z_Axis) + + @staticmethod + def translate(cs_old, direction): + """Translates a CoordinateSystem by a given direction vector. + + #### Parameters: + - `cs_old` (CoordinateSystem): The original coordinate system to be translated. + - `direction` (Vector3): The direction vector along which the coordinate system is to be translated. + + #### Returns: + `CoordinateSystem`: A new CoordinateSystem object translated from the original one. + + #### Example usage: + ```python + + ``` + """ + + from abstract.vector import Vector3 + new_origin = Point.translate(cs_old.Origin, direction) + + X_axis = Vector3(1, 0, 0) + + Y_Axis = Vector3(0, 1, 0) + + Z_Axis = Vector3(0, 0, 1) + + CSNew = CoordinateSystem( + new_origin, x_axis=X_axis, y_axis=Y_Axis, z_axis=Z_Axis) + + CSNew.Origin = new_origin + return CSNew + + def __eq__(self, other): + return self.x == other.x and self.y == other.y and self.z == other.z + + def __str__(self): + return f"{__class__.__name__}(Origin = " + f"{self.Origin}, X_axis = {self.Xaxis}, Y_Axis = {self.Y_axis}, Z_Axis = {self.Z_axis})" + + @staticmethod + def by_point_main_vector(self, new_origin_coordinatesystem: Point, DirectionVectorZ): + """Creates a new CoordinateSystem at a given point, oriented along a specified direction vector. + This method establishes a new coordinate system by defining its origin and its Z-axis direction. The X and Y axes are determined based on the given Z-axis to form a right-handed coordinate system. If the calculated X or Y axis has a zero length (in cases of alignment with the global Z-axis), default axes are used. + + #### Parameters: + - `new_origin_coordinatesystem` (`Point`): The origin point of the new coordinate system. + - `DirectionVectorZ` (Vector3): The direction vector that defines the Z-axis of the new coordinate system. + + #### Returns: + `CoordinateSystem`: A new CoordinateSystem object oriented along the specified direction vector with its origin at the given point. + + #### Example usage: + ```python + + ``` + """ + from abstract.vector import Vector3 + vz = DirectionVectorZ + vz = Vector3.normalize(vz) + vx = Vector3.perpendicular(vz)[0] + try: + vx = Vector3.normalize(vx) + except: + vx = Vector3(1, 0, 0) + vy = Vector3.perpendicular(vz)[1] + try: + vy = Vector3.normalize(vy) + except: + vy = Vector3(0, 1, 0) + CSNew = CoordinateSystem(new_origin_coordinatesystem, vx, vy, vz) + return CSNew + + @staticmethod + def move_local(cs_old, x: float, y: float, z: float): + """Moves a CoordinateSystem in its local coordinate space by specified displacements. + + #### Parameters: + - `cs_old` (CoordinateSystem): The original coordinate system to be moved. + - `x` (float): The displacement along the local X-axis. + - `y` (float): The displacement along the local Y-axis. + - `z` (float): The displacement along the local Z-axis. + + #### Returns: + `CoordinateSystem`: A new CoordinateSystem object moved in its local coordinate space. + + #### Example usage: + ```python + + ``` + """ + + from abstract.vector import Vector3 + + xloc_vect_norm = cs_old.Xaxis + xdisp = Vector3.scale(xloc_vect_norm, x) + yloc_vect_norm = cs_old.Xaxis + ydisp = Vector3.scale(yloc_vect_norm, y) + zloc_vect_norm = cs_old.Xaxis + zdisp = Vector3.scale(zloc_vect_norm, z) + disp = Vector3.sum3(xdisp, ydisp, zdisp) + CS = CoordinateSystem.translate(cs_old, disp) + return CS + + @staticmethod + def translate_origin(origin1, origin2): + """Calculates the translation needed to move from one origin to another. + + #### Parameters: + - `origin1` (Point): The starting origin point. + - `origin2` (Point): The ending origin point. + + #### Returns: + `Point`: A new Point object representing the translated origin. + + #### Example usage: + ```python + + ``` + """ + origin1_n = Point.to_matrix(origin1) + origin2_n = Point.to_matrix(origin2) + + new_origin_n = origin1_n + (origin2_n - origin1_n) + return Point(new_origin_n[0], new_origin_n[1], new_origin_n[2]) + + @staticmethod + def calculate_rotation_matrix(xaxis_1, yaxis_1, zaxis_1, xaxis_2, yaxis_2, zaxis_2): + """Calculates the rotation matrix needed to align one coordinate system with another. + + #### Parameters: + - `xaxis_1`, `yaxis_1`, `zaxis_1` (Vector3): The axes of the initial coordinate system. + - `xaxis_2`, `yaxis_2`, `zaxis_2` (Vector3): The axes of the target coordinate system. + + #### Returns: + Rotation Matrix (list of lists): A matrix representing the rotation needed to align the first coordinate system with the second. + + #### Example usage: + ```python + + ``` + """ + from abstract.vector import Vector3 + + R1 = [Vector3.to_matrix(xaxis_1), Vector3.to_matrix( + yaxis_1), Vector3.to_matrix(zaxis_1)] + + R2 = [Vector3.to_matrix(xaxis_2), Vector3.to_matrix( + yaxis_2), Vector3.to_matrix(zaxis_2)] + + R1_transposed = list(map(list, zip(*R1))) + R2_transposed = list(map(list, zip(*R2))) + + rotation_matrix = Vector3.dot_product(Vector3.from_matrix( + R2_transposed), Vector3.length(Vector3.from_matrix(R1_transposed))) + return rotation_matrix + + @staticmethod + def normalize(point: Point) -> list: + """Normalizes a vector to have a length of 1. + This method calculates the normalized (unit) version of a given vector, making its length equal to 1 while preserving its direction. If the input vector has a length of 0 (i.e., it is a zero vector), the method returns the original vector. + + #### Parameters: + - `point` (list of float): A vector represented as a list of three floats, corresponding to its x, y, and z components, respectively. + + #### Returns: + list of float: The normalized vector as a list of three floats. If the original vector is a zero vector, returns the original vector. + + #### Example usage: + ```python + + ``` + """ + norm = (point[0]**2 + point[1]**2 + point[2]**2)**0.5 + return [point[0] / norm, point[1] / norm, point[2] / norm] if norm > 0 else point + + +def transform_point(point_local: Point, coordinate_system_old: CoordinateSystem, new_origin: Point, direction_vector) -> Point: + """Transforms a point from one coordinate system to another based on a new origin and a direction vector. + This function calculates the new position of a point when the coordinate system is changed. The new coordinate system is defined by a new origin point and a direction vector that specifies the orientation of the Z-axis. The X and Y axes are computed to form a right-handed coordinate system. This method takes into account the original position of the point in the old coordinate system to accurately calculate its position in the new coordinate system. + + #### Parameters: + - `point_local` (Point): The point to be transformed, given in the local coordinate system. + - `coordinate_system_old` (CoordinateSystem): The original coordinate system the point is in. + - `new_origin` (Point): The origin of the new coordinate system. + - `direction_vector` (Vector3): The direction vector defining the new Z-axis of the coordinate system. + + #### Returns: + Point: The transformed point in the new coordinate system. + + #### Example usage: + ```python + + ``` + """ + from abstract.vector import Vector3 + + direction_vector = Vector3.to_matrix(direction_vector) + new_origin = Point.to_matrix(new_origin) + vz_norm = Vector3.length(Vector3(*direction_vector)) + vz = [direction_vector[0] / vz_norm, direction_vector[1] / + vz_norm, direction_vector[2] / vz_norm] + + vx = [-vz[1], vz[0], 0] + vx_norm = Vector3.length(Vector3(*vx)) + + if vx_norm == 0: + vx = [1, 0, 0] + else: + vx = [vx[0] / vx_norm, vx[1] / vx_norm, vx[2] / vx_norm] + + vy = Vector3.cross_product(Vector3(*vz), Vector3(*vx)) + vy_norm = Vector3.length(vy) + if vy_norm != 0: + vy = [vy.x / vy_norm, vy.y / vy_norm, vy.z / vy_norm] + else: + vy = [0, 1, 0] + + point_1 = point_local + CSNew = CoordinateSystem(Point.from_matrix(new_origin), Vector3.from_matrix( + vx), Vector3.from_matrix(vy), Vector3.from_matrix(vz)) + vector_1 = Point.difference(coordinate_system_old.Origin, CSNew.Origin) + + vector_2 = Vector3.product(point_1.x, CSNew.Xaxis) + vector_3 = Vector3.product(point_1.y, CSNew.Y_axis) + vector_4 = Vector3.product(point_1.z, CSNew.Z_axis) + vtot = Vector3(vector_1.x + vector_2.x + vector_3.x + vector_4.x, vector_1.y + vector_2.y + + vector_3.y + vector_4.y, vector_1.z + vector_2.z + vector_3.z + vector_4.z) + pointNew = Point.translate(Point(0, 0, 0), vtot) + + return pointNew + + +def transform_point_2(PointLocal: Point, CoordinateSystemNew: CoordinateSystem) -> Point: + """Transforms a point from its local coordinate system to a new coordinate system. + This function translates a point based on its local coordinates (x, y, z) within its current coordinate system to a new position in a specified coordinate system. The transformation involves scaling the local coordinates by the axes vectors of the new coordinate system and sequentially translating the point along these axes vectors starting from the new origin. + + #### Parameters: + - `PointLocal` (`Point`): The point in its local coordinate system to be transformed. + - `CoordinateSystemNew` (`CoordinateSystem`): The new coordinate system to which the point is to be transformed. + + #### Returns: + `Point`: The point transformed into the new coordinate system. + + #### Example usage: + ```python + + ``` + """ + from abstract.vector import Vector3 + pn = Point.translate(CoordinateSystemNew.Origin, Vector3.scale( + CoordinateSystemNew.Xaxis, PointLocal.x)) + pn2 = Point.translate(pn, Vector3.scale( + CoordinateSystemNew.Y_axis, PointLocal.y)) + pn3 = Point.translate(pn2, Vector3.scale( + CoordinateSystemNew.Z_axis, PointLocal.z)) + return pn3 + +class Vector2: + def __init__(self, x, y) -> None: + self.id = generateID() + self.type = __class__.__name__ + self.x: float = 0.0 + self.y: float = 0.0 + self.x = x + self.y = y + + def serialize(self): + id_value = str(self.id) if not isinstance( + self.id, (str, int, float)) else self.id + return { + 'id': id_value, + 'type': self.type, + 'x': self.x, + 'y': self.y + } + + @staticmethod + def deserialize(data): + x = data['x'] + y = data['y'] + return Vector2(x, y) + + @staticmethod + def by_two_points(p1, p2): + return Vector2( + p2.x-p1.x, + p2.y-p1.y + ) + + @staticmethod + def length(v1): + return math.sqrt(v1.x * v1.x + v1.y * v1.y) + + @staticmethod + def scale(v1, scalefactor): + return Vector2( + v1.x * scalefactor, + v1.y * scalefactor + ) + + @staticmethod + def normalize(v1, axis=-1, order=2): + v1_mat = Vector2.to_matrix(v1) + l2_norm = math.sqrt(v1_mat[0]**2 + v1_mat[1]**2) + if l2_norm == 0: + l2_norm = 1 + + normalized_v = [v1_mat[0] / l2_norm, v1_mat[1] / l2_norm] + + return Vector2(normalized_v[0], normalized_v[1]) + + @staticmethod + def to_matrix(self): + return [self.x, self.y] + + @staticmethod + def from_matrix(self): + return Vector2(self[0], self[1]) + + @staticmethod # inwendig product, if zero, then vectors are perpendicular + def dot_product(v1, v2): + return v1.x*v2.x+v1.y*v2.y + + @staticmethod + def angle_between(v1, v2): + return math.degrees(math.acos((Vector2.dot_product(v1, v2)/(Vector2.length(v1) * Vector2.length(v2))))) + + @staticmethod + def angle_radian_between(v1, v2): + return math.acos((Vector2.dot_product(v1, v2)/(Vector2.length(v1) * Vector2.length(v2)))) + + @staticmethod # Returns vector perpendicular on the two vectors + def cross_product(v1, v2): + return Vector3( + v1.y - v2.y, + v2.x - v1.x, + v1.x*v2.y - v1.y*v2.x + ) + + @staticmethod + def reverse(v1): + return Vector2( + v1.x*-1, + v1.y*-1 + ) + + @staticmethod + def sum(vector_1: 'Vector2', vector_2: 'Vector2') -> 'Vector2': + """Adds two vectors element-wise. + + #### Parameters: + - `vector_1` (Vector2): First vector. + - `vector_2` (Vector2): Second vector. + + Returns: + `Vector2`: Sum of the two input vectors. + + #### Example usage: + + ```python + vector_1 = Vector2(19, 18) + vector_2 = Vector2(8, 17) + output = Vector2.sum(vector_1, vector_2) + # Vector3() + ``` + """ + return Vector2( + vector_1.x + vector_2.x, + vector_1.y + vector_2.y + ) + + def __id__(self): + return f"id:{self.id}" + + def __str__(self) -> str: + return f"{__class__.__name__}(X = {self.x:.3f}, Y = {self.y:.3f})" + + +class Point2D: + def __init__(self, x: float, y: float) -> None: + self.id = generateID() + self.type = __class__.__name__ + self.x = x + self.y = y + self.x = float(x) + self.y = float(y) + self.value = self.x, self.y + self.units = "mm" + + def serialize(self): + id_value = str(self.id) if not isinstance( + self.id, (str, int, float)) else self.id + return { + 'id': id_value, + 'type': self.type, + 'x': self.x, + 'y': self.y + } + + @staticmethod + def deserialize(data): + x = data['x'] + y = data['y'] + return Point2D(x, y) + + def __id__(self): + return f"id:{self.id}" + + def translate(self, vector: Vector2): + x = self.x + vector.x + y = self.y + vector.y + p1 = Point2D(x, y) + return p1 + + @staticmethod + def dot_product(p1, p2): + return p1.x*p2.x+p1.y*p2.y + + def rotate(self, rotation): + x = self.x + y = self.y + r = math.sqrt(x * x + y * y) + rotationstart = math.degrees(math.atan2(y, x)) + rotationtot = rotationstart + rotation + xn = round(math.cos(math.radians(rotationtot)) * r, 3) + yn = round(math.sin(math.radians(rotationtot)) * r, 3) + p1 = Point2D(xn, yn) + return p1 + + def __str__(self) -> str: + return f"{__class__.__name__}(X = {self.x:.3f}, Y = {self.y:.3f})" + + @staticmethod + def distance(point1, point2): + return math.sqrt((point1.x - point2.x)**2 + (point1.y - point2.y)**2) + + @staticmethod + def midpoint(point1, point2): + return Point2D((point2.x-point1.x)/2, (point2.y-point1.y)/2) + + @staticmethod + def to_pixel(point1, Xmin, Ymin, TotalWidth, TotalHeight, ImgWidthPix: int, ImgHeightPix: int): + # Convert Point to pixel on a image given a deltaX, deltaY, Width of the image etc. + x = point1.x + y = point1.y + xpix = math.floor(((x - Xmin) / TotalWidth) * ImgWidthPix) + # min vanwege coord stelsel Image.Draw + ypix = ImgHeightPix - \ + math.floor(((y - Ymin) / TotalHeight) * ImgHeightPix) + return xpix, ypix + + +def transform_point_2D(PointLocal1: Point2D, CoordinateSystemNew: CoordinateSystem): + # Transform point from Global Coordinatesystem to a new Coordinatesystem + # CSold = CSGlobal + from abstract.vector import Vector3 + from geometry.point import Point + PointLocal = Point(PointLocal1.x, PointLocal1.y, 0) + # pn = Point.translate(CoordinateSystemNew.Origin, Vector3.scale(CoordinateSystemNew.Xaxis, PointLocal.x)) + # pn2 = Point2D.translate(pn, Vector3.scale(CoordinateSystemNew.Y_axis, PointLocal.y)) + pn3 = Point2D.translate(PointLocal, Vector2( + CoordinateSystemNew.Origin.x, CoordinateSystemNew.Origin.y)) + # pn3 = Point2D(pn.x,pn.y) + return pn3 + + +class Line2D: + def __init__(self, start, end) -> None: + self.type = __class__.__name__ + self.start: Point2D = start + self.end: Point2D = end + self.x = [self.start.x, self.end.x] + self.y = [self.start.y, self.end.y] + self.dx = self.start.x-self.end.x + self.dy = self.start.y-self.end.y + self.vector2: Vector2 = Vector2.by_two_points(self.start, self.end) + self.vector2_normalised = Vector2.normalize(self.vector2) + self.length = self.length() + self.id = generateID() + + def serialize(self): + return { + 'type': self.type, + 'start': self.start.serialize(), + 'end': self.end.serialize(), + 'x': self.x, + 'y': self.y, + 'dx': self.dx, + 'dy': self.dy, + 'length': self.length, + 'id': self.id + } + + @staticmethod + def deserialize(data): + start_point = Point2D.deserialize(data['start']) + end_point = Point2D.deserialize(data['end']) + return Line2D(start_point, end_point) + + def __id__(self): + return f"id:{self.id}" + + def mid_point(self): + vect = Vector2.scale(self.vector2, 0.5) + mid = Point2D.translate(self.start, vect) + return mid + + def length(self): + return math.sqrt(math.sqrt(self.dx * self.dx + self.dy * self.dy) * math.sqrt(self.dx * self.dx + self.dy * self.dy)) + + def f_line(self): + # returns line for Folium(GIS) + return [[self.start.y, self.start.x], [self.end.y, self.end.x]] + + def __str__(self): + return f"{__class__.__name__}(" + f"Start: {self.start}, End: {self.end})" + + +# class Arc2D: +# def __init__(self, pntxy1, pntxy2, pntxy3) -> None: +# self.id = generateID() +# self.type = __class__.__name__ +# self.start: Point2D = pntxy1 +# self.mid: Point2D = pntxy2 +# self.end: Point2D = pntxy3 +# self.origin = self.origin_arc() +# self.angle_radian = self.angle_radian() +# self.radius = self.radius_arc() +# self.normal = Vector3(0, 0, 1) +# self.xdir = Vector3(1, 0, 0) +# self.ydir = Vector3(0, 1, 0) +# self.coordinatesystem = self.coordinatesystem_arc() + +# def serialize(self): +# id_value = str(self.id) if not isinstance( +# self.id, (str, int, float)) else self.id +# return { +# 'id': id_value, +# 'type': self.type, +# 'start': self.start.serialize(), +# 'mid': self.mid.serialize(), +# 'end': self.end.serialize(), +# 'origin': self.origin, +# 'angle_radian': self.angle_radian, +# 'coordinatesystem': self.coordinatesystem +# } + +# @staticmethod +# def deserialize(data): +# start_point = Point2D.deserialize(data['start']) +# mid_point = Point2D.deserialize(data['mid']) +# end_point = Point2D.deserialize(data['end']) +# arc = Arc2D(start_point, mid_point, end_point) + +# arc.origin = data.get('origin') +# arc.angle_radian = data.get('angle_radian') +# arc.coordinatesystem = data.get('coordinatesystem') + +# return arc + +# def __id__(self): +# return f"id:{self.id}" + +# def points(self): +# # returns point on the curve +# return (self.start, self.mid, self.end) + +# def coordinatesystem_arc(self): +# vx2d = Vector2.by_two_points(self.origin, self.start) # Local X-axe +# vx = Vector3(vx2d.x, vx2d.y, 0) +# vy = Vector3(vx.y, vx.x * -1, 0) +# vz = Vector3(0, 0, 1) +# self.coordinatesystem = CoordinateSystem(self.origin, Vector3.normalize( +# vx), Vector3.normalize(vy), Vector3.normalize(vz)) +# return self.coordinatesystem + +# def angle_radian(self): +# v1 = Vector2.by_two_points(self.origin, self.end) +# v2 = Vector2.by_two_points(self.origin, self.start) +# angle = Vector2.angle_radian_between(v1, v2) +# return angle + +# def origin_arc(self): +# # calculation of origin of arc #Todo can be simplified for sure +# Vstartend = Vector2.by_two_points(self.start, self.end) +# halfVstartend = Vector2.scale(Vstartend, 0.5) +# # half distance between start and end +# b = 0.5 * Vector2.length(Vstartend) +# try: +# # distance from start-end line to origin +# x = math.sqrt(Arc2D.radius_arc(self) * +# Arc2D.radius_arc(self) - b * b) +# except: +# x = 0 +# mid = Point2D.translate(self.start, halfVstartend) +# v2 = Vector2.by_two_points(self.mid, mid) +# v3 = Vector2.normalize(v2) +# tocenter = Vector2.scale(v3, x) +# center = Point2D.translate(mid, tocenter) +# self.origin = center +# return center + +# def radius_arc(self): +# a = Vector2.length(Vector2.by_two_points(self.start, self.mid)) +# b = Vector2.length(Vector2.by_two_points(self.mid, self.end)) +# c = Vector2.length(Vector2.by_two_points(self.end, self.start)) +# s = (a + b + c) / 2 +# A = math.sqrt(s * (s-a) * (s-b) * (s-c)) +# R = (a * b * c) / (4 * A) +# return R + +# @staticmethod +# def points_at_parameter(arc, count: int): +# # ToDo can be simplified. Now based on the 3D variant +# d_alpha = arc.angle_radian / (count - 1) +# alpha = 0 +# pnts = [] +# for i in range(count): +# pnts.append(Point2D(arc.radius * math.cos(alpha), +# arc.radius * math.sin(alpha))) +# alpha = alpha + d_alpha +# CSNew = arc.coordinatesystem +# pnts2 = [] +# for i in pnts: +# pnts2.append(transform_point_2D(i, CSNew)) +# return pnts2 + +# @staticmethod +# def segmented_arc(arc, count): +# pnts = Arc2D.points_at_parameter(arc, count) +# i = 0 +# lines = [] +# for j in range(len(pnts)-1): +# lines.append(Line2D(pnts[i], pnts[i+1])) +# i = i + 1 +# return lines + +# def __str__(self): +# return f"{__class__.__name__}({self.start},{self.mid},{self.end})" + + +class Arc2D: + def __init__(self, startPoint: 'Point2D', midPoint: 'Point2D', endPoint: 'Point2D') -> 'Arc2D': + """Initializes an Arc object with start, mid, and end points. + This constructor calculates and assigns the arc's origin, plane, radius, start angle, end angle, angle in radians, area, length, units, and coordinate system based on the input points. + + - `startPoint` (Point2D): The starting point of the arc. + - `midPoint` (Point2D): The mid point of the arc which defines its curvature. + - `endPoint` (Point2D): The ending point of the arc. + """ + self.id = generateID() + self.type = __class__.__name__ + self.start = startPoint + self.mid = midPoint + self.end = endPoint + self.origin = self.origin_arc() + vector_1 = Vector3(x=1, y=0, z=0) + vector_2 = Vector3(x=0, y=1, z=0) + self.plane = Plane.by_two_vectors_origin( + vector_1, + vector_2, + self.origin + ) + self.radius = self.radius_arc() + self.startAngle = 0 + self.endAngle = 0 + self.angle_radian = self.angle_radian() + self.area = 0 + self.length = self.length() + self.units = project.units + self.coordinatesystem = None #self.coordinatesystem_arc() + + def distance(self, point_1: 'Point2D', point_2: 'Point2D') -> float: + """Calculates the Euclidean distance between two points in 3D space. + + #### Parameters: + - `point_1` (Point2D): The first point. + - `point_2` (Point2D): The second point. + + #### Returns: + `float`: The Euclidean distance between `point_1` and `point_2`. + + #### Example usage: + ```python + point1 = Point2D(1, 2) + point2 = Point2D(4, 5) + distance = arc.distance(point1, point2) + # distance will be the Euclidean distance between point1 and point2 + ``` + """ + return math.sqrt((point_2.x - point_1.x) ** 2 + (point_2.y - point_1.y) ** 2) + + def coordinatesystem_arc(self) -> 'CoordinateSystem': + """Calculates and returns the coordinate system of the arc. + The coordinate system is defined by the origin of the arc and the normalized vectors along the local X, Y, and Z axes. + + #### Returns: + `CoordinateSystem`: The coordinate system of the arc. + + #### Example usage: + ```python + coordinatesystem = arc.coordinatesystem_arc() + # coordinatesystem will be an instance of CoordinateSystem representing the arc's local coordinate system + ``` + """ + vx = Vector2.by_two_points(self.origin, self.start) # Local X-axe + vector_2 = Vector2.by_two_points(self.end, self.origin) + vz = Vector2.cross_product(vx, vector_2) # Local Z-axe + vy = Vector2.cross_product(vx, vz) # Local Y-axe + self.coordinatesystem = CoordinateSystem(self.origin, Vector2.normalize(vx), Vector2.normalize(vy), + Vector2.normalize(vz)) + return self.coordinatesystem + + def radius_arc(self) -> 'float': + """Calculates and returns the radius of the arc. + The radius is computed based on the distances between the start, mid, and end points of the arc. + + #### Returns: + `float`: The radius of the arc. + + #### Example usage: + ```python + radius = arc.radius_arc() + # radius will be the calculated radius of the arc + ``` + """ + a = self.distance(self.start, self.mid) + b = self.distance(self.mid, self.end) + c = self.distance(self.end, self.start) + s = (a + b + c) / 2 + A = math.sqrt(s * (s - a) * (s - b) * (s - c)) + R = (a * b * c) / (4 * A) + return R + + def origin_arc(self) -> 'Point2D': + """Calculates and returns the origin of the arc. + The origin is calculated based on the geometric properties of the arc defined by its start, mid, and end points. + + #### Returns: + `Point`: The calculated origin point of the arc. + + #### Example usage: + ```python + origin = arc.origin_arc() + # origin will be the calculated origin point of the arc + ``` + """ + # calculation of origin of arc #Todo can be simplified for sure + Vstartend = Vector2.by_two_points(self.start, self.end) + halfVstartend = Vector2.scale(Vstartend, 0.5) + # half distance between start and end + b = 0.5 * Vector2.length(Vstartend) + # distance from start-end line to origin + # print(Arc2D.radius_arc(self), Arc2D.radius_arc(self), b) + try: + x = math.sqrt(Arc2D.radius_arc(self) * Arc2D.radius_arc(self) - b * b) + except: + x = 0 + mid = Point2D.translate(self.start, halfVstartend) + vector_2 = Vector2.by_two_points(self.mid, mid) + vector_3 = Vector2.normalize(vector_2) + tocenter = Vector2.scale(vector_3, x) + center = Point2D.translate(mid, tocenter) + return center + + def angle_radian(self) -> 'float': + """Calculates and returns the total angle of the arc in radians. + The angle is determined based on the vectors defined by the start, mid, and end points with respect to the arc's origin. + + #### Returns: + `float`: The total angle of the arc in radians. + + #### Example usage: + ```python + angle = arc.angle_radian() + # angle will be the total angle of the arc in radians + ``` + """ + vector_1 = Vector2.by_two_points(self.origin, self.end) + vector_2 = Vector2.by_two_points(self.origin, self.start) + vector_3 = Vector2.by_two_points(self.origin, self.mid) + vector_4 = Vector2.sum(vector_1, vector_2) + try: + v4b = Vector2.new_length(vector_4, self.radius) + if Vector2.value(vector_3) == Vector2.value(v4b): + angle = Vector2.angle_radian_between(vector_1, vector_2) + else: + angle = 2*math.pi-Vector2.angle_radian_between(vector_1, vector_2) + return angle + except: + angle = 2*math.pi-Vector2.angle_radian_between(vector_1, vector_2) + return angle + + def length(self) -> 'float': + """Calculates and returns the length of the arc. + The length is calculated using the geometric properties of the arc defined by its start, mid, and end points. + + #### Returns: + `float`: The length of the arc. + + #### Example usage: + ```python + length = arc.length() + # length will be the calculated length of the arc + ``` + """ + x1, y1, z1 = self.start.x, self.start.y, 0 + x2, y2, z2 = self.mid.x, self.mid.y, 0 + x3, y3, z3 = self.end.x, self.end.y, 0 + + r1 = ((x2 - x1) ** 2 + (y2 - y1) ** 2 + (z2 - z1) ** 2) ** 0.5 / 2 + a = math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2 + (z2 - z1) ** 2) + b = math.sqrt((x3 - x2) ** 2 + (y3 - y2) ** 2 + (z3 - z2) ** 2) + c = math.sqrt((x3 - x1) ** 2 + (y3 - y1) ** 2 + (z3 - z1) ** 2) + cos_angle = (a ** 2 + b ** 2 - c ** 2) / (2 * a * b) + m1 = math.acos(cos_angle) + arc_length = r1 * m1 + + return arc_length + + @staticmethod + def points_at_parameter(arc: 'Arc2D', count: 'int') -> 'list': + """Generates a list of points along the arc at specified intervals. + This method divides the arc into segments based on the `count` parameter and calculates points at these intervals along the arc. + + #### Parameters: + - `arc` (Arc2D): The arc object. + - `count` (int): The number of points to generate along the arc. + + #### Returns: + `list`: A list of points (`Point2D` objects) along the arc. + + #### Example usage: + ```python + arc = Arc2D(startPoint, midPoint, endPoint) + points = Arc2D.points_at_parameter(arc, 5) + # points will be a list of 5 points along the arc + ``` + """ + # Create points at parameter on an arc based on an interval + d_alpha = arc.angle_radian / (count - 1) + alpha = 0 + pnts = [] + for i in range(count): + pnts.append(Point2D(arc.radius * math.cos(alpha), + arc.radius * math.sin(alpha), 0)) + alpha = alpha + d_alpha + cs_new = arc.coordinatesystem + pnts2 = [] # transformed points + for i in pnts: + pnts2.append(transform_point_2(i, cs_new)) + return pnts2 + + @staticmethod + def segmented_arc(arc: 'Arc2D', count: 'int') -> 'list': + """Divides the arc into segments and returns a list of line segments. + This method uses the `points_at_parameter` method to generate points along the arc at specified intervals and then creates line segments between these consecutive points. + + #### Parameters: + - `arc` (Arc2D): The arc object. + - `count` (int): The number of segments (and thus the number of points - 1) to create. + + #### Returns: + `list`: A list of line segments (`Line` objects) representing the divided arc. + + #### Example usage: + ```python + arc = Arc2D(startPoint, midPoint, endPoint) + segments = Arc2D.segmented_arc(arc, 3) + # segments will be a list of 2 lines dividing the arc into 3 segments + ``` + """ + pnts = Arc2D.points_at_parameter(arc, count) + i = 0 + lines = [] + for j in range(len(pnts) - 1): + lines.append(Line2D(pnts[i], pnts[i + 1])) + i = i + 1 + return lines + + def draw_arc_point(cx: 'float', cy: 'float', radius: 'float', angle_degrees: 'float') -> 'Point2D': + """ + Calculates a point on the arc given its center, radius, and an angle in degrees. + + Parameters: + - cx (float): The x-coordinate of the arc's center. + - cy (float): The y-coordinate of the arc's center. + - radius (float): The radius of the arc. + - angle_degrees (float): The angle in degrees from the start point of the arc, + measured clockwise from the positive x-axis. + + Returns: + Point2D: The calculated point on the arc represented as a `Point2D` object with the calculated x and y coordinates. + """ + angle_radians = math.radians(angle_degrees) + x = cx + radius * math.cos(angle_radians) + y = cy + radius * math.sin(angle_radians) + return Point2D(x, y) + + + def __str__(self) -> 'str': + """Generates a string representation of the Arc2D object. + + #### Returns: + `str`: A string that represents the Arc2D object. + + #### Example usage: + ```python + arc = Arc2D(startPoint, midPoint, endPoint) + print(arc) + # Output: Arc2D() + ``` + """ + return f"{__class__.__name__}()" + + +class PolyCurve2D: + def __init__(self) -> None: + self.id = generateID() + self.type = __class__.__name__ + self.curves = [] + self.points2D = [] + self.segmentcurves = None + self.width = None + self.height = None + self.approximateLength = None + self.graphicsStyleId = None + self.isClosed = None + self.isCyclic = None + self.isElementGeometry = None + self.isReadOnly = None + self.length = self.length() + self.period = None + self.reference = None + self.visibility = None + + def serialize(self): + curves_serialized = [curve.serialize() if hasattr( + curve, 'serialize') else str(curve) for curve in self.curves] + points_serialized = [point.serialize() if hasattr( + point, 'serialize') else str(point) for point in self.points2D] + + return { + 'type': self.type, + 'curves': curves_serialized, + 'points2D': points_serialized, + 'segmentcurves': self.segmentcurves, + 'width': self.width, + 'height': self.height, + 'approximateLength': self.approximateLength, + 'graphicsStyleId': self.graphicsStyleId, + 'id': self.id, + 'isClosed': self.isClosed, + 'isCyclic': self.isCyclic, + 'isElementGeometry': self.isElementGeometry, + 'isReadOnly': self.isReadOnly, + 'period': self.period, + 'reference': self.reference, + 'visibility': self.visibility + } + + @staticmethod + def deserialize(data): + polycurve = PolyCurve2D() + polycurve.segmentcurves = data.get('segmentcurves') + polycurve.width = data.get('width') + polycurve.height = data.get('height') + polycurve.approximateLength = data.get('approximateLength') + polycurve.graphicsStyleId = data.get('graphicsStyleId') + polycurve.id = data.get('id') + polycurve.isClosed = data.get('isClosed') + polycurve.isCyclic = data.get('isCyclic') + polycurve.isElementGeometry = data.get('isElementGeometry') + polycurve.isReadOnly = data.get('isReadOnly') + polycurve.period = data.get('period') + polycurve.reference = data.get('reference') + polycurve.visibility = data.get('visibility') + + if 'curves' in data: + for curve_data in data['curves']: + curve = Line2D.deserialize(curve_data) + polycurve.curves.append(curve) + + if 'points2D' in data: + for point_data in data['points2D']: + point = Point2D.deserialize(point_data) + polycurve.points2D.append(point) + + return polycurve + + def __id__(self): + return f"id:{self.id}" + + @classmethod # curves or curves? + def by_joined_curves(cls, curves): + if not curves or len(curves) < 1: + raise ValueError( + "At least one curve is required to create a PolyCurve2D.") + + polycurve = cls() + for curve in curves: + if not polycurve.points2D or polycurve.points2D[-1] != curve.start: + polycurve.points2D.append(curve.start) + polycurve.curves.append(curve) + polycurve.points2D.append(curve.end) + + polycurve.isClosed = polycurve.points2D[0].value == polycurve.points2D[-1].value + if project.autoclose == True and polycurve.isClosed == False: + polycurve.curves.append( + Line2D(start=curves[-1].end, end=curves[0].start)) + polycurve.points2D.append(curves[0].start) + polycurve.isClosed = True + return polycurve + + def points(self): + for i in self.curves: + self.points2D.append(i.start) + self.points2D.append(i.end) + return self.points2D + + def centroid(self) -> Point2D: + if not self.isClosed or len(self.points2D) < 3: + return "Polygon has less than 3 points or is not closed!" + + num_points = len(self.points2D) + signed_area = 0 + centroid_x = 0 + centroid_y = 0 + + for i in range(num_points): + x0, y0 = self.points2D[i].x, self.points2D[i].y + if i == num_points - 1: + x1, y1 = self.points2D[0].x, self.points2D[0].y + else: + x1, y1 = self.points2D[i + 1].x, self.points2D[i + 1].y + + cross = x0 * y1 - x1 * y0 + signed_area += cross + centroid_x += (x0 + x1) * cross + centroid_y += (y0 + y1) * cross + + signed_area *= 0.5 + centroid_x /= (6.0 * signed_area) + centroid_y /= (6.0 * signed_area) + + return Point2D(x=round(centroid_x, project.decimals), y=round(centroid_y, project.decimals)) + + @staticmethod + def from_polycurve_3D(PolyCurve): + points = [] + for pt in PolyCurve.points: + points.append(Point2D(pt.x, pt.y)) + plycrv = PolyCurve2D.by_points(points) + return plycrv + + def area(self) -> float: # shoelace formula + if not self.isClosed or len(self.points2D) < 3: + return "Polygon has less than 3 points or is not closed!" + + num_points = len(self.points2D) + area = 0 + + for i in range(num_points): + x0, y0 = self.points2D[i].x, self.points2D[i].y + if i == num_points - 1: + x1, y1 = self.points2D[0].x, self.points2D[0].y + else: + x1, y1 = self.points2D[i + 1].x, self.points2D[i + 1].y + + area += x0 * y1 - x1 * y0 + + area = abs(area) / 2.0 + return area + + def close(self) -> bool: + if self.curves[0] == self.curves[-1]: + return self + else: + self.curves.append(self.curves[0]) + plycrv = PolyCurve2D() + for curve in self.curves: + plycrv.curves.append(curve) + return plycrv + + def scale(self, scalefactor): + crvs = [] + for i in self.curves: + if i.__class__.__name__ == "Arc": + arcie = Arc2D(Point2D.product(scalefactor, i.start), + Point2D.product(scalefactor, i.end)) + arcie.mid = Point2D.product(scalefactor, i.mid) + crvs.append(arcie) + elif i.__class__.__name__ == "Line": + crvs.append(Line2D(Point2D.product( + scalefactor, i.start), Point2D.product(scalefactor, i.end))) + else: + print("Curvetype not found") + crv = PolyCurve2D.by_joined_curves(crvs) + return crv + + @classmethod + def by_points(cls, points): + if not points or len(points) < 2: + pass + + polycurve = cls() + for i in range(len(points)): + polycurve.points2D.append(points[i]) + if i < len(points) - 1: + polycurve.curves.append( + Line2D(start=points[i], end=points[i+1])) + + polycurve.isClosed = points[0] == points[-1] + if project.autoclose == True: + polycurve.curves.append(Line2D(start=points[-1], end=points[0])) + polycurve.points2D.append(points[0]) + polycurve.isClosed = True + return polycurve + + def get_width(self) -> float: + x_values = [point.x for point in self.points2D] + y_values = [point.y for point in self.points2D] + + min_x = min(x_values) + max_x = max(x_values) + min_y = min(y_values) + max_y = max(y_values) + + left_top = Point2D(x=min_x, y=max_y) + left_bottom = Point2D(x=min_x, y=min_y) + right_top = Point2D(x=max_x, y=max_y) + right_bottom = Point2D(x=max_x, y=min_y) + self.width = abs(Point2D.distance(left_top, right_top)) + self.height = abs(Point2D.distance(left_top, left_bottom)) + return self.width + + def length(self) -> float: + lst = [] + for line in self.curves: + lst.append(line.length) + + return sum(i.length for i in self.curves) + + @staticmethod + def by_polycurve_2D(PolyCurve2D): + plycrv = PolyCurve2D() + curves = [] + for i in PolyCurve2D.curves: + if i.__class__.__name__ == "Arc2D": + curves.append(Arc2D(Point2D(i.start.x, i.start.y), Point2D( + i.mid.x, i.mid.y), Point2D(i.end.x, i.end.y))) + elif i.__class__.__name__ == "Line2D": + curves.append( + Line2D(Point2D(i.start.x, i.start.y), Point2D(i.end.x, i.end.y))) + else: + print("Curvetype not found") + pnts = [] + for i in curves: + pnts.append(i.start) + pnts.append(curves[0].start) + plycrv.points = pnts + plycrv.curves = curves + return plycrv + + def multi_split(self, lines: Line2D): + lines = flatten(lines) + new_polygons = [] + for index, line in enumerate(lines): + if index == 0: + n_p = self.split(line, returnlines=True) + if n_p != None: + for nxp in n_p: + if nxp != None: + new_polygons.append(n_p) + else: + for new_poly in flatten(new_polygons): + n_p = new_poly.split(line, returnlines=True) + if n_p != None: + for nxp in n_p: + if nxp != None: + new_polygons.append(n_p) + project.objects.append(flatten(new_polygons)) + return flatten(new_polygons) + + def translate(self, vector2d: Vector2): + crvs = [] + v1 = vector2d + for i in self.curves: + if i.__class__.__name__ == "Arc2D": + crvs.append(Arc2D(i.start.translate(v1), + i.mid.translate(v1), i.end.translate(v1))) + elif i.__class__.__name__ == "Line2D": + crvs.append(Line2D(i.start.translate(v1), i.end.translate(v1))) + else: + print("Curvetype not found") + crv = PolyCurve2D.by_joined_curves(crvs) + return crv + + @staticmethod + def copy_translate(pc, vector3d: Vector3): + crvs = [] + v1 = vector3d + for i in pc.curves: + if i.__class__.__name__ == "Line": + crvs.append(Line2D(Point2D.translate(i.start, v1), + Point2D.translate(i.end, v1))) + else: + print("Curvetype not found") + + PCnew = PolyCurve2D.by_joined_curves(crvs) + return PCnew + + def rotate(self, rotation): + crvs = [] + for i in self.curves: + if i.__class__.__name__ == "Arc2D": + crvs.append(Arc2D(i.start.rotate(rotation), + i.mid.rotate(rotation), i.end.rotate(rotation))) + elif i.__class__.__name__ == "Line2D": + crvs.append(Line2D(i.start.rotate( + rotation), i.end.rotate(rotation))) + else: + print("Curvetype not found") + crv = PolyCurve2D.by_joined_curves(crvs) + return crv + + @staticmethod + def boundingbox_global_CS(PC): + x = [] + y = [] + for i in PC.curves(): + x.append(i.start.x) + y.append(i.start.y) + xmin = min(x) + xmax = max(x) + ymin = min(y) + ymax = max(y) + bbox = PolyCurve2D.by_points([Point2D(xmin, ymin), Point2D( + xmax, ymin), Point2D(xmax, ymax), Point2D(xmin, ymax), Point2D(xmin, ymin)]) + return bbox + + @staticmethod + def bounds(PC): + # returns xmin,xmax,ymin,ymax,width,height of polycurve 2D + x = [] + y = [] + for i in PC.curves: + x.append(i.start.x) + y.append(i.start.y) + xmin = min(x) + xmax = max(x) + ymin = min(y) + ymax = max(y) + width = xmax-xmin + height = ymax-ymin + return xmin, xmax, ymin, ymax, width, height + + @classmethod + def unclosed_by_points(self, points: Point2D): + plycrv = PolyCurve2D() + for index, point in enumerate(points): + plycrv.points2D.append(point) + try: + nextpoint = points[index + 1] + plycrv.curves.append(Line2D(start=point, end=nextpoint)) + except: + pass + return plycrv + + @staticmethod + def polygon(self): + points = [] + for i in self.curves: + if i == Arc2D: + points.append(i.start, i.mid) + else: + points.append(i.start) + points.append(points[0]) + return points + + @staticmethod + def segment(self, count): + crvs = [] + for i in self.curves: + if i.__class__.__name__ == "Arc2D": + crvs.append(Arc2D.segmented_arc(i, count)) + elif i.__class__.__name__ == "Line2D": + crvs.append(i) + crv = flatten(crvs) + pc = PolyCurve2D.by_joined_curves(crv) + return pc + + def to_polycurve_3D(self): + from geometry.geometry2d import PolyCurve2D + from geometry.geometry2d import Point2D + from geometry.geometry2d import Line2D + from geometry.geometry2d import Arc2D + + p1 = PolyCurve2D() + curves = [] + for i in self.curves: + if i.__class__.__name__ == "Arc": + curves.append(Arc2D(Point2D(i.start.x, i.start.y), Point2D(i.middle.x, i.middle.y), + Point2D(i.end.x, i.end.y))) + elif i.__class__.__name__ == "Line": + curves.append( + Line2D(Point2D(i.start.x, i.start.y), Point2D(i.end.x, i.end.y))) + else: + print("Curvetype not found") + pnts = [] + for i in curves: + pnts.append(i.start) + pnts.append(curves[0].start) + p1.points2D = pnts + p1.curves = curves + return p1 + + @staticmethod + def transform_from_origin(polycurve, startpoint: Point2D, directionvector: Vector3): + crvs = [] + for i in polycurve.curves: + if i.__class__.__name__ == "Arc2D": + crvs.append(Arc2D(transform_point_2D(i.start, project.CSGlobal, startpoint, directionvector), + transform_point_2D( + i.mid, project.CSGlobal, startpoint, directionvector), + transform_point_2D( + i.end, project.CSGlobal, startpoint, directionvector) + )) + elif i.__class__.__name__ == "Line2D": + crvs.append(Line2D(start=transform_point_2D(i.start, project.CSGlobal, startpoint, directionvector), + end=transform_point_2D( + i.end, project.CSGlobal, startpoint, directionvector) + )) + else: + print(i.__class__.__name__ + "Curvetype not found") + pc = PolyCurve2D() + pc.curves = crvs + return pc + + def __str__(self): + l = len(self.points2D) + return f"{__class__.__name__}, ({l} points)" + + +class Surface2D: + def __init__(self) -> None: + pass # PolyCurve2D + self.id = generateID() + self.type = __class__.__name__ + + def __id__(self): + return f"id:{self.id}" + + def __str__(self) -> str: + return f"{__class__.__name__}({self})" + + +class Profile2D: + def __init__(self) -> None: + self.id = generateID() + self.type = __class__.__name__ + + def __id__(self): + return f"id:{self.id}" + + def __str__(self) -> str: + return f"{__class__.__name__}({self})" + + +class ParametricProfile2D: + def __init__(self) -> None: + self.type = __class__.__name__ + self.id = generateID() + + def __id__(self): + return f"id:{self.id}" + + def __str__(self) -> str: + return f"{__class__.__name__}({self})" + +# Rule: line, whitespace, line whitespace etc., scale +HiddenLine1 = ["Hidden Line 1", [1, 1], 100] +# Rule: line, whitespace, line whitespace etc., scale +HiddenLine2 = ["Hidden Line 2", [2, 1], 100] +# Rule: line, whitespace, line whitespace etc., scale +Centerline = ["Center Line 1", [8, 2, 2, 2], 100] + + +def line_to_pattern(baseline: 'Line', pattern_obj) -> 'list': + """Converts a baseline (Line object) into a list of line segments based on a specified pattern. + This function takes a line (defined by its start and end points) and a pattern object. The pattern object defines a repeating sequence of segments to be applied along the baseline. The function calculates the segments according to the pattern and returns a list of Line objects representing these segments. + + #### Parameters: + - `baseline` (Line): The baseline along which the pattern is to be applied. This line is defined by its start and end points. + - `pattern_obj` (Pattern): The pattern object defining the sequence of segments. The pattern object should have the following structure: + - An integer representing the number of repetitions. + - A list of floats representing the lengths of each segment in the pattern. + - A float representing the scale factor for the lengths of the segments in the pattern. + + #### Returns: + `list`: A list of Line objects that represent the line segments created according to the pattern along the baseline. + + #### Example usage: + ```python + baseline = Line(Point(0, 0, 0), Point(10, 0, 0)) + pattern_obj = (3, [2, 1], 1) # 3 repetitions, pattern of lengths 2 and 1, scale factor 1 + patterned_lines = line_to_pattern(baseline, pattern_obj) + # patterned_lines will be a list of Line objects according to the pattern + ``` + + The function works by calculating the total length of the pattern, the number of whole lengths of the pattern that fit into the baseline, and then generating the line segments according to these calculations. If the end of the baseline is reached before completing a pattern sequence, the last segment is adjusted to end at the baseline's end point. + """ + # this converts a line to list of lines based on a pattern + origin = baseline.start + dir = Vector3.by_two_points(baseline.start, baseline.end) + unityvect = Vector3.normalize(dir) + + Pattern = pattern_obj + l = baseline.length + patternlength = sum(Pattern[1]) * Pattern[2] + # number of whole lengths of the pattern + count = math.floor(l / patternlength) + lines = [] + + startpoint = origin + ll = 0 + rl = 10000 + for i in range(count + 1): + n = 0 + for i in Pattern[1]: + deltaV = Vector3.product(i * Pattern[2], unityvect) + dl = Vector3.length(deltaV) + if rl < dl: # this is the last line segment on the line where the pattern is going to be cut into pieces. + endpoint = baseline.end + else: + endpoint = Point.translate(startpoint, deltaV) + if n % 2: + a = 1 + 1 + else: + lines.append(Line(start=startpoint, end=endpoint)) + if rl < dl: + break # end of line reached + startpoint = endpoint + n = n + 1 + ll = ll + dl # total length + rl = l - ll # remaining length within the pattern + startpoint = startpoint + return lines + +class Line: + def __init__(self, start: 'Point', end: 'Point') -> 'Line': + """Initializes a Line object with the specified start and end points. + + - `start` (Point): The starting point of the line segment. + - `end` (Point): The ending point of the line segment. + """ + self.id = generateID() + self.type = __class__.__name__ + self.start: Point = start + self.end: Point = end + self.x = [self.start.x, self.end.x] + self.y = [self.start.y, self.end.y] + try: + self.z = [self.start.z, self.end.z] + except: + self.z = 0 + + self.dx = self.end.x-self.start.x + self.dy = self.end.y-self.start.y + try: + self.dz = self.end.z-self.start.z + except: + self.dz = 0 + self.length = self.length() + self.vector: Vector3 = Vector3.by_two_points(start, end) + self.vector_normalised = Vector3.normalize(self.vector) + + def serialize(self) -> 'dict': + """Serializes the Line object into a dictionary. + + #### Returns: + `dict`: A dictionary containing the serialized data of the Line object. + + #### Example usage: + ```python + + ``` + """ + id_value = str(self.id) if not isinstance( + self.id, (str, int, float)) else self.id + start_serialized = self.start.serialize() if hasattr( + self.start, 'serialize') else str(self.start) + end_serialized = self.end.serialize() if hasattr( + self.end, 'serialize') else str(self.end) + + # Serialize vector if it has a serialize method, otherwise convert to string representation + vector_serialized = self.vector.serialize() if hasattr( + self.vector, 'serialize') else str(self.vector) + vector_normalized_serialized = self.vector_normalised.serialize() if hasattr( + self.vector_normalised, 'serialize') else str(self.vector_normalised) + + return { + 'id': id_value, + 'type': self.type, + 'start': start_serialized, + 'end': end_serialized, + 'x': self.x, + 'y': self.y, + 'z': self.z, + 'dx': self.dx, + 'dy': self.dy, + 'dz': self.dz, + 'length': self.length, + 'vector': vector_serialized, + 'vector_normalised': vector_normalized_serialized + } + + @staticmethod + def deserialize(data: 'dict'): + """Deserializes the data dictionary into a Line object. + + #### Parameters: + - `data` (dict): The dictionary containing the serialized data of the Line object. + + #### Returns: + `Line`: A Line object reconstructed from the serialized data. + + #### Example usage: + ```python + + ``` + """ + start_point = Point.deserialize(data['start']) + end_point = Point.deserialize(data['end']) + + instance = Line(start_point, end_point) + + instance.id = data.get('id') + instance.type = data.get('type') + instance.x = data.get('x') + instance.y = data.get('y') + instance.z = data.get('z') + instance.dx = data.get('dx') + instance.dy = data.get('dy') + instance.dz = data.get('dz') + instance.length = data.get('length') + + if 'vector' in data and hasattr(Vector3, 'deserialize'): + instance.vector = Vector3.deserialize(data['vector']) + else: + instance.vector = data['vector'] + + if 'vector_normalised' in data and hasattr(Vector3, 'deserialize'): + instance.vector_normalised = Vector3.deserialize( + data['vector_normalised']) + else: + instance.vector_normalised = data['vector_normalised'] + + return instance + + @staticmethod + def by_startpoint_direction_length(start: 'Point', direction: 'Vector3', length: 'float') -> 'Line': + """Creates a line segment starting from a given point in the direction of a given vector with a specified length. + + #### Parameters: + - `start` (Point): The starting point of the line segment. + - `direction` (Vector3): The direction vector of the line segment. + - `length` (float): The length of the line segment. + + #### Returns: + `Line`: A new Line object representing the line segment. + + #### Example usage: + ```python + + ``` + """ + norm = math.sqrt(direction.x ** 2 + direction.y ** + 2 + direction.z ** 2) + normalized_direction = Vector3( + direction.x / norm, direction.y / norm, direction.z / norm) + + end_x = start.x + normalized_direction.x * length + end_y = start.y + normalized_direction.y * length + end_z = start.z + normalized_direction.z * length + end_point = Point(end_x, end_y, end_z) + + return Line(start, end_point) + + def translate(self, direction: 'Vector3') -> 'Line': + """Translates the Line object by a given direction vector. + + #### Parameters: + - `direction` (Vector3): The direction vector by which the line segment will be translated. + + #### Returns: + `Line`: The translated Line object. + + #### Example usage: + ```python + + ``` + """ + self.start = Point.translate(self.start, direction) + self.end = Point.translate(self.end, direction) + return self + + @staticmethod + def translate_2(line: 'Line', direction: 'Vector3') -> 'Line': + """Translates the specified Line object by a given direction vector. + + #### Parameters: + - `line` (Line): The Line object to be translated. + - `direction` (Vector3): The direction vector by which the line segment will be translated. + + #### Returns: + `Line`: The translated Line object. + + #### Example usage: + ```python + + ``` + """ + line.start = Point.translate(line.start, direction) + line.end = Point.translate(line.end, direction) + return line + + @staticmethod + def transform(line: 'Line', cs_new: 'CoordinateSystem') -> 'Line': + """Transforms the Line object to a new coordinate system. + + #### Parameters: + - `line` (Line): The Line object to be transformed. + - `cs_new` (CoordinateSystem): The new coordinate system to which the Line object will be transformed. + + #### Returns: + `Line`: The transformed Line object. + + #### Example usage: + ```python + + ``` + """ + ln = Line(start=line.start, end=line.end) + ln.start = transform_point_2(ln.start, cs_new) + ln.end = transform_point_2(ln.end, cs_new) + return ln + + def offset(line: 'Line', vector: 'Vector3') -> 'Line': + """Offsets the Line object by a given vector. + + #### Parameters: + - `line` (Line): The Line object to be offset. + - `vector` (Vector3): The vector by which the Line object will be offset. + + #### Returns: + `Line`: The offset Line object. + + #### Example usage: + ```python + + ``` + """ + start = Point(line.start.x + vector.x, line.start.y + + vector.y, line.start.z + vector.z) + end = Point(line.end.x + vector.x, line.end.y + + vector.y, line.end.z + vector.z) + return Line(start=start, end=end) + + # @classmethod + def point_at_parameter(self, interval: 'float' = None) -> 'Point': + """Computes the point on the Line object at a specified parameter value. + + #### Parameters: + - `interval` (float): The parameter value determining the point on the line. Default is None, which corresponds to the midpoint. + + #### Returns: + `Point`: The point on the Line object corresponding to the specified parameter value. + + #### Example usage: + ```python + + ``` + """ + if interval == None: + interval = 0.0 + x1, y1, z1 = self.start.x, self.start.y, self.start.z + x2, y2, z2 = self.end.x, self.end.y, self.end.z + if float(interval) == 0.0: + return self.start + else: + devBy = 1/interval + return Point((x1 + x2) / devBy, (y1 + y2) / devBy, (z1 + z2) / devBy) + + def mid_point(self) -> 'Point': + """Computes the midpoint of the Line object. + + #### Returns: + `Point`: The midpoint of the Line object. + + #### Example usage: + ```python + + ``` + """ + vect = Vector3.scale(self.vector, 0.5) + mid = Point.translate(self.start, vect) + return mid + + def split(self, points: 'Union[Point, list[Point]]') -> 'list[Line]': + """Splits the Line object at the specified point(s). + + #### Parameters: + - `points` (Point or List[Point]): The point(s) at which the Line object will be split. + + #### Returns: + `List[Line]`: A list of Line objects resulting from the split operation. + + #### Example usage: + ```python + + ``` + """ + lines = [] + if isinstance(points, list): + points.extend([self.start, self.end]) + sorted_points = sorted( + points, key=lambda p: p.distance(p, self.end)) + lines = create_lines(sorted_points) + return lines + elif isinstance(points, Point): + point = points + lines.append(Line(start=self.start, end=point)) + lines.append(Line(start=point, end=self.end)) + return lines + + def length(self) -> 'float': + """Computes the length of the Line object. + + #### Returns: + `float`: The length of the Line object. + + #### Example usage: + ```python + + ``` + """ + return math.sqrt(math.sqrt(self.dx * self.dx + self.dy * self.dy) * math.sqrt(self.dx * self.dx + self.dy * self.dy) + self.dz * self.dz) + + def __str__(self) -> 'str': + """Returns a string representation of the Line object. + + #### Returns: + `str`: A string representation of the Line object. + + #### Example usage: + ```python + + ``` + """ + return f"{__class__.__name__}(" + f"{self.start}, {self.end})" + + +def create_lines(points: 'list[Point]') -> 'list[Line]': + """Creates a list of Line objects from a list of points. + This function generates line segments connecting consecutive points in the list. + + #### Parameters: + - `points` (List[Point]): A list of points. + + #### Returns: + `List[Line]`: A list of Line objects representing the line segments connecting the points. + + #### Example usage: + ```python + + ``` + """ + lines = [] + for i in range(len(points)-1): + line = Line(points[i], points[i+1]) + lines.append(line) + return lines + + +class PolyCurve: + def __init__(self): + """Initializes a PolyCurve object. + + - `id` (int): The unique identifier of the arc. + - `type` (str): The type of the arc. + - `start` (Point): The start point of the arc. + - `mid` (Point): The mid point of the arc. + - `end` (Point): The end point of the arc. + - `origin` (Point): The origin point of the arc. + - `plane` (Plane): The plane containing the arc. + - `radius` (float): The radius of the arc. + - `startAngle` (float): The start angle of the arc in radians. + - `endAngle` (float): The end angle of the arc in radians. + - `angle_radian` (float): The total angle of the arc in radians. + - `area` (float): The area of the arc. + - `length` (float): The length of the arc. + - `units` (str): The units used for measurement. + - `coordinatesystem` (CoordinateSystem): The coordinate system of the arc. + + """ + self.id = generateID() + self.type = __class__.__name__ + self.curves = [] + self.points = [] + self.segmentcurves = None + self.width = None + self.height = None + # Methods () + # self.close + # pointonperimeter + # Properties + self.approximateLength = None + self.graphicsStyleId = None + self.isClosed = None + self.isCyclic = None + self.isElementGeometry = None + self.isReadOnly = None + self.length = self.length() + self.period = None + self.reference = None + self.visibility = None + + def serialize(self) -> 'dict': + """Serializes the PolyCurve object. + + #### Returns: + `dict`: Serialized data of the PolyCurve object. + + #### Example usage: + ```python + + ``` + """ + curves_serialized = [curve.serialize() if hasattr( + curve, 'serialize') else str(curve) for curve in self.curves] + points_serialized = [point.serialize() if hasattr( + point, 'serialize') else str(point) for point in self.points] + + return { + 'type': self.type, + 'curves': curves_serialized, + 'points': points_serialized, + 'segmentcurves': self.segmentcurves, + 'width': self.width, + 'height': self.height, + 'approximateLength': self.approximateLength, + 'graphicsStyleId': self.graphicsStyleId, + 'id': self.id, + 'isClosed': self.isClosed, + 'isCyclic': self.isCyclic, + 'isElementGeometry': self.isElementGeometry, + 'isReadOnly': self.isReadOnly, + 'period': self.period, + 'reference': self.reference, + 'visibility': self.visibility + } + + @staticmethod + def deserialize(data): + """Deserializes the PolyCurve object. + + #### Parameters: + - `data` (dict): Serialized data of the PolyCurve object. + + #### Returns: + `PolyCurve`: Deserialized PolyCurve object. + + #### Example usage: + ```python + + ``` + """ + polycurve = PolyCurve() + polycurve.segmentcurves = data.get('segmentcurves') + polycurve.width = data.get('width') + polycurve.height = data.get('height') + polycurve.approximateLength = data.get('approximateLength') + polycurve.graphicsStyleId = data.get('graphicsStyleId') + polycurve.id = data.get('id') + polycurve.isClosed = data.get('isClosed') + polycurve.isCyclic = data.get('isCyclic') + polycurve.isElementGeometry = data.get('isElementGeometry') + polycurve.isReadOnly = data.get('isReadOnly') + polycurve.period = data.get('period') + polycurve.reference = data.get('reference') + polycurve.visibility = data.get('visibility') + + # Deserialize curves and points + if 'curves' in data: + for curve_data in data['curves']: + # Assuming a deserialize method exists for curve objects + curve = Line.deserialize(curve_data) + polycurve.curves.append(curve) + + if 'points' in data: + for point_data in data['points']: + # Assuming a deserialize method exists for point objects + point = Point.deserialize(point_data) + polycurve.points.append(point) + + return polycurve + + def scale(self, scale_factor: 'float') -> 'PolyCurve': + """Scales the PolyCurve object by the given factor. + + #### Parameters: + - `scale_factor`: The scaling factor. + + #### Returns: + `PolyCurve`: Scaled PolyCurve object. + + #### Example usage: + ```python + + ``` + """ + crvs = [] + for i in self.curves: + if i.__class__.__name__ == "Arc": + arcie = Arc(Point.product(scale_factor, i.start), + Point.product(scale_factor, i.end)) + arcie.mid = Point.product(scale_factor, i.mid) + crvs.append(arcie) + elif i.__class__.__name__ == "Line": + crvs.append(Line(Point.product(scale_factor, i.start), + Point.product(scale_factor, i.end))) + else: + print("Curvetype not found") + crv = PolyCurve.by_joined_curves(crvs) + return crv + + def get_width(self) -> 'float': + """Calculates the width of the PolyCurve. + + #### Returns: + `float`: The width of the PolyCurve. + + #### Example usage: + ```python + + ``` + """ + x_values = [point.x for point in self.points] + y_values = [point.y for point in self.points] + + min_x = min(x_values) + max_x = max(x_values) + min_y = min(y_values) + max_y = max(y_values) + + left_top = Point(x=min_x, y=max_y, z=self.z) + left_bottom = Point(x=min_x, y=min_y, z=self.z) + right_top = Point(x=max_x, y=max_y, z=self.z) + right_bottom = Point(x=max_x, y=min_y, z=self.z) + self.width = abs(Point.distance(left_top, right_top)) + self.height = abs(Point.distance(left_top, left_bottom)) + return self.width + + def centroid(self) -> 'Point': + """Calculates the centroid of the PolyCurve. + + #### Returns: + `Point`: The centroid point of the PolyCurve. + + #### Example usage: + ```python + + ``` + """ + if self.isClosed: + num_points = len(self.points) + if num_points < 3: + return "Polygon has less than 3 points!" + + A = 0.0 + for i in range(num_points): + x0, y0 = self.points[i].x, self.points[i].y + x1, y1 = self.points[( + i + 1) % num_points].x, self.points[(i + 1) % num_points].y + A += x0 * y1 - x1 * y0 + A *= 0.5 + + Cx, Cy = 0.0, 0.0 + for i in range(num_points): + x0, y0 = self.points[i].x, self.points[i].y + x1, y1 = self.points[( + i + 1) % num_points].x, self.points[(i + 1) % num_points].y + factor = (x0 * y1 - x1 * y0) + Cx += (x0 + x1) * factor + Cy += (y0 + y1) * factor + + Cx /= (6.0 * A) + Cy /= (6.0 * A) + + return Point(x=round(Cx, project.decimals), y=round(Cy, project.decimals), z=self.points[0].z) + else: + return None + + def area(self) -> 'float': # shoelace formula + """Calculates the area enclosed by the PolyCurve using the shoelace formula. + + #### Returns: + `float`: The area enclosed by the PolyCurve. + + #### Example usage: + ```python + + ``` + """ + if self.isClosed: + if len(self.points) < 3: + return "Polygon has less than 3 points!" + + num_points = len(self.points) + S1, S2 = 0, 0 + + for i in range(num_points): + x, y = self.points[i].x, self.points[i].y + if i == num_points - 1: + x_next, y_next = self.points[0].x, self.points[0].y + else: + x_next, y_next = self.points[i + 1].x, self.points[i + 1].y + + S1 += x * y_next + S2 += y * x_next + + area = 0.5 * abs(S1 - S2) + return area + else: + print("Polycurve is not closed, no area!") + return None + + def length(self) -> 'float': + """Calculates the total length of the PolyCurve. + + #### Returns: + `float`: The total length of the PolyCurve. + + #### Example usage: + ```python + + ``` + """ + lst = [] + for line in self.curves: + lst.append(line.length) + + return sum(i.length for i in self.curves) + + def close(self) -> 'bool': + """Closes the PolyCurve by connecting the last point to the first point. + + #### Returns: + `bool`: True if the PolyCurve is successfully closed, False otherwise. + + #### Example usage: + ```python + + ``` + """ + if self.curves[0] == self.curves[-1]: + return self + else: + self.curves.append(self.curves[0]) + plycrv = PolyCurve() + for curve in self.curves: + plycrv.curves.append(curve) + return plycrv + + @classmethod + def by_joined_curves(self, curvelst: 'list[Line]') -> 'PolyCurve': + """Creates a PolyCurve from a list of joined Line curves. + + #### Parameters: + - `curvelst` (list[Line]): The list of Line curves to join. + + #### Returns: + `PolyCurve`: The created PolyCurve object. + + #### Example usage: + ```python + + ``` + """ + for crv in curvelst: + if crv.length == 0: + curvelst.remove(crv) + # print("Error: Curve length cannot be zero.") + # sys.exit() + + projectClosed = project.closed + plycrv = PolyCurve() + for index, curve in enumerate(curvelst): + if index == 0: + plycrv.curves.append(curve) + plycrv.points.append(curve.start) + plycrv.points.append(curve.end) + else: + plycrv.curves.append(curve) + plycrv.points.append(curve.end) + if projectClosed: + if plycrv.points[0].value == plycrv.points[-1].value: + plycrv.isClosed = True + else: + # plycrv.points.append(curvelst[0].start) + plycrv.curves.append(curve) + plycrv.isClosed = True + elif projectClosed == False: + if plycrv.points[0].value == plycrv.points[-1].value: + plycrv.isClosed = True + else: + plycrv.isClosed = False + if plycrv.points[-2].value == plycrv.points[0].value: + plycrv.curves = plycrv.curves.pop(-1) + + return plycrv + + @classmethod + def by_points(self, points: 'list[Point]') -> 'PolyCurve': + """Creates a PolyCurve from a list of points. + + #### Parameters: + - `points` (list[Point]): The list of points defining the PolyCurve. + + #### Returns: + `PolyCurve`: The created PolyCurve object. + + #### Example usage: + ```python + + ``` + """ + seen = set() + unique_points = [] + + for point in points: + if point in seen: + points.remove(point) + print("Error: Polycurve cannot have multiple identical points.") + sys.exit() + + seen.add(point) + unique_points.append(point) + + plycrv = PolyCurve() + for index, point in enumerate(points): + plycrv.points.append(point) + try: + nextpoint = points[index+1] + plycrv.curves.append(Line(start=point, end=nextpoint)) + except: + firstpoint = points[0] + plycrv.curves.append(Line(start=point, end=firstpoint)) + + if project.closed: + if plycrv.points[0].value == plycrv.points[-1].value: + plycrv.isClosed = True + else: + plycrv.isClosed = True + plycrv.points.append(points[0]) + + elif project.closed == False: + if plycrv.points[0].value == plycrv.points[-1].value: + plycrv.isClosed = True + else: + plycrv.isClosed = False + plycrv.points.append(points[0]) + + return plycrv + + @classmethod + def unclosed_by_points(cls, points: 'list[Point]') -> 'PolyCurve': + """Creates an unclosed PolyCurve from a list of points. + + #### Parameters: + - `points` (list[Point]): The list of points defining the PolyCurve. + + #### Returns: + `PolyCurve`: The created unclosed PolyCurve object. + + #### Example usage: + ```python + + ``` + """ + plycrv = PolyCurve() + for index, point in enumerate(points): + plycrv.points.append(point) + try: + nextpoint = points[index + 1] + plycrv.curves.append(Line(start=point, end=nextpoint)) + except: + pass + return plycrv + + @staticmethod + # Create segmented polycurve. Arcs, elips will be translated to straight lines + def segment(self, count: 'int') -> 'PolyCurve': + """Segments the PolyCurve into straight lines. + + #### Parameters: + - `count` (int): The number of segments. + + #### Returns: + `PolyCurve`: The segmented PolyCurve object. + + #### Example usage: + ```python + + ``` + """ + crvs = [] # add isClosed + for i in self.curves: + if i.__class__.__name__ == "Arc": + crvs.append(Arc.segmented_arc(i, count)) + elif i.__class__.__name__ == "Line": + crvs.append(i) + crv = flatten(crvs) + pc = PolyCurve.by_joined_curves(crv) + return pc + + @staticmethod + def by_polycurve_2D(PolyCurve2D) -> 'PolyCurve': + """Creates a 3D PolyCurve from a 2D PolyCurve. + + #### Parameters: + - `PolyCurve2D`: The 2D PolyCurve object. + + #### Returns: + `PolyCurve`: The created 3D PolyCurve object. + + #### Example usage: + ```python + + ``` + """ + plycrv = PolyCurve() + curves = [] + for i in PolyCurve2D.curves: + if i.__class__.__name__ == "Arc2D": + curves.append(Arc(Point(i.start.x, i.start.y, 0), Point( + i.mid.x, i.mid.y, 0), Point(i.end.x, i.end.y, 0))) + elif i.__class__.__name__ == "Line2D": + curves.append(Line(Point(i.start.x, i.start.y, 0), + Point(i.end.x, i.end.y, 0))) + else: + print("Curvetype not found") + pnts = [] + for i in curves: + pnts.append(i.start) + pnts.append(curves[0].start) + plycrv.points = pnts + plycrv.curves = curves + return plycrv + + # make sure that the lines start/stop already on the edge of the polycurve + def split(self, line: 'Line', returnlines=None) -> 'list[PolyCurve]': + """Splits the PolyCurve by a line and returns the split parts. + + #### Parameters: + - `line` (Line): The line to split the PolyCurve. + - `returnlines` (bool, optional): Whether to return the split PolyCurves as objects or add them to the project. Defaults to None. + + #### Returns: + `list[PolyCurve]`: If `returnlines` is True, returns a list of split PolyCurves. Otherwise, None. + + #### Example usage: + ```python + + ``` + """ + from abstract.intersect2d import is_point_on_line_segment + from abstract.intersect2d import get_intersect_polycurve_lines + + allLines = self.curves.copy() + + insect = get_intersect_polycurve_lines( + self, line, split=True, stretch=False) + for pt in insect["IntersectGridPoints"]: + for index, line in enumerate(allLines): + if is_point_on_line_segment(pt, line) == True: + cuttedLines = line.split([pt]) + allLines = replace_at_index(allLines, index, cuttedLines) + + if len(insect["IntersectGridPoints"]) == 2: + part1 = [] + part2 = [] + + for j in allLines: + # part1 + if j.start == insect["IntersectGridPoints"][1]: + part1LineEnd = j.end + part1.append(j.start) + if j.end == insect["IntersectGridPoints"][0]: + part1LineStart = j.start + part1.append(j.end) + # part2 + if j.start == insect["IntersectGridPoints"][0]: + part2LineEnd = j.end + part2.append(j.start) + if j.end == insect["IntersectGridPoints"][1]: + part2LineStart = j.start + part2.append(j.end) + + s2 = self.points.index(part1LineStart) + s1 = self.points.index(part1LineEnd) + completelist = list(range(len(self.points))) + partlist1 = flatten(completelist[s2:s1+1]) + partlist2 = flatten([completelist[s1+1:]] + [completelist[:s2]]) + + SplittedPolyCurves = [] + # part1 + if part1LineStart != None and part1LineEnd != None: + for i, index in enumerate(partlist1): + pts = self.points[index] + part1.insert(i+1, pts) + if returnlines: + SplittedPolyCurves.append(PolyCurve.by_points(part1)) + else: + project.objects.append(PolyCurve.by_points(part1)) + + # part2 -> BUGG? + if part2LineStart != None and part2LineEnd != None: + for index in partlist2: + pts = self.points[index] + part2.insert(index, pts) + if returnlines: + SplittedPolyCurves.append(PolyCurve.by_points(part2)) + else: + project.objects.append(PolyCurve.by_points(part2)) + + if returnlines: # return lines while using multi_split + return SplittedPolyCurves + + else: + print( + f"Must need 2 points to split PolyCurve into PolyCurves, got now {len(insect['IntersectGridPoints'])} points.") + + def multi_split(self, lines: 'Line') -> 'list[PolyCurve]': # SLOW, MUST INCREASE SPEAD + """Splits the PolyCurve by multiple lines. + This method splits the PolyCurve by multiple lines and adds the resulting PolyCurves to the project. + + #### Parameters: + - `lines` (List[Line]): The list of lines to split the PolyCurve. + + #### Returns: + `List[PolyCurve]`: The list of split PolyCurves. + + #### Example usage: + ```python + + ``` + """ + lines = flatten(lines) + new_polygons = [] + for index, line in enumerate(lines): + if index == 0: + n_p = self.split(line, returnlines=True) + if n_p != None: + for nxp in n_p: + if nxp != None: + new_polygons.append(n_p) + else: + for new_poly in flatten(new_polygons): + n_p = new_poly.split(line, returnlines=True) + if n_p != None: + for nxp in n_p: + if nxp != None: + new_polygons.append(n_p) + project.objects.append(flatten(new_polygons)) + return flatten(new_polygons) + + def translate(self, vector_3d: 'Vector3') -> 'PolyCurve': + """Translates the PolyCurve by a 3D vector. + + #### Parameters: + - `vector_3d` (Vector3): The 3D vector by which to translate the PolyCurve. + + #### Returns: + `PolyCurve`: The translated PolyCurve. + + #### Example usage: + ```python + + ``` + """ + crvs = [] + vector_1 = vector_3d + for i in self.curves: + if i.__class__.__name__ == "Arc": + crvs.append(Arc(Point.translate(i.start, vector_1), Point.translate( + i.middle, vector_1), Point.translate(i.end, vector_1))) + elif i.__class__.__name__ == "Line": + crvs.append(Line(Point.translate(i.start, vector_1), + Point.translate(i.end, vector_1))) + else: + print("Curvetype not found") + pc = PolyCurve() + pc.curves = crvs + return pc + + @staticmethod + def copy_translate(pc: 'PolyCurve', vector_3d: 'Vector3') -> 'PolyCurve': + """Creates a copy of a PolyCurve and translates it by a 3D vector. + + #### Parameters: + - `pc` (PolyCurve): The PolyCurve to copy and translate. + - `vector_3d` (Vector3): The 3D vector by which to translate the PolyCurve. + + #### Returns: + `PolyCurve`: The translated copy of the PolyCurve. + + #### Example usage: + ```python + + ``` + """ + crvs = [] + vector_1 = vector_3d + for i in pc.curves: + # if i.__class__.__name__ == "Arc": + # crvs.append(Arc(Point.translate(i.start, vector_1), Point.translate(i.middle, vector_1), Point.translate(i.end, vector_1))) + if i.__class__.__name__ == "Line": + crvs.append(Line(Point.translate(i.start, vector_1), + Point.translate(i.end, vector_1))) + else: + print("Curvetype not found") + + PCnew = PolyCurve.by_joined_curves(crvs) + return PCnew + + def rotate(self, angle: 'float', dz: 'float') -> 'PolyCurve': + """Rotates the PolyCurve by a given angle around the Z-axis and displaces it in the Z-direction. + + #### Parameters: + - `angle` (float): The angle of rotation in degrees. + - `dz` (float): The displacement in the Z-direction. + + #### Returns: + `PolyCurve`: The rotated and displaced PolyCurve. + + #### Example usage: + ```python + + ``` + """ + # angle in degrees + # dz = displacement in z-direction + crvs = [] + for i in self.curves: + if i.__class__.__name__ == "Arc": + crvs.append(Arc(Point.rotate_XY(i.start, angle, dz), Point.rotate_XY( + i.middle, angle, dz), Point.rotate_XY(i.end, angle, dz))) + elif i.__class__.__name__ == "Line": + crvs.append(Line(Point.rotate_XY(i.start, angle, dz), + Point.rotate_XY(i.end, angle, dz))) + else: + print("Curvetype not found") + crv = PolyCurve.by_joined_curves(crvs) + return crv + + def to_polycurve_2D(self): + """Converts the PolyCurve to a PolyCurve2D. + + #### Returns: + `PolyCurve2D`: The converted PolyCurve2D. + + #### Example usage: + ```python + + ``` + """ + # by points, + from geometry.geometry2d import PolyCurve2D + from geometry.geometry2d import Point2D + from geometry.geometry2d import Line2D + from geometry.geometry2d import Arc2D + + point_1 = PolyCurve2D() + count = 0 + curves = [] + for i in self.curves: + if i.__class__.__name__ == "Arc": + curves.append(Arc2D(Point2D(i.start.x, i.start.y), Point2D(i.middle.x, i.middle.y), + Point2D(i.end.x, i.end.y))) + elif i.__class__.__name__ == "Line": + curves.append( + Line2D(Point2D(i.start.x, i.start.y), Point2D(i.end.x, i.end.y))) + else: + print("Curvetype not found") + pnts = [] + for i in curves: + pnts.append(i.start) + pnts.append(curves[0].start) + point_1.points = pnts + point_1.curves = curves + return point_1 + + @staticmethod + def transform_from_origin(polycurve: 'PolyCurve', startpoint: 'Point', directionvector: 'Vector3') -> 'PolyCurve': + """Transforms a PolyCurve from a given origin point and direction vector. + + #### Parameters: + - `polycurve` (PolyCurve): The PolyCurve to transform. + - `startpoint` (Point): The origin point for the transformation. + - `directionvector` (Vector3): The direction vector for the transformation. + + #### Returns: + `PolyCurve`: The transformed PolyCurve. + + #### Example usage: + ```python + + ``` + """ + + if polycurve.type == "PolyCurve2D": + crvs = [] + pnts = [] + for i in polycurve.curves: + if i.__class__.__name__ == "Arc" or i.__class__.__name__ == "Arc2D": + start_transformed = transform_point(Point.point_2D_to_3D(i.start) if i.__class__.__name__ == "Arc2D" else i.start, CSGlobal, startpoint, directionvector) + mid_transformed = transform_point(Point.point_2D_to_3D(i.mid) if i.__class__.__name__ == "Arc2D" else i.mid, CSGlobal, startpoint, directionvector) + end_transformed = transform_point(Point.point_2D_to_3D(i.end) if i.__class__.__name__ == "Arc2D" else i.end, CSGlobal, startpoint, directionvector) + + crvs.append(Arc(start_transformed, mid_transformed, end_transformed)) + pnts.extend([start_transformed, mid_transformed, end_transformed]) + + elif i.__class__.__name__ == "Line" or i.__class__.__name__ == "Line2D": + start_transformed = transform_point(Point.point_2D_to_3D(i.start) if i.__class__.__name__ == "Line2D" else i.start, CSGlobal, startpoint, directionvector) + end_transformed = transform_point(Point.point_2D_to_3D(i.end) if i.__class__.__name__ == "Line2D" else i.end, CSGlobal, startpoint, directionvector) + + crvs.append(Line(start_transformed, end_transformed)) + pnts.extend([start_transformed, end_transformed]) + + else: + print(i.__class__.__name__ + " Curvetype not found") + + # if i.__class__.__name__ == "Arc": + # crvs.append(Arc(transform_point(i.start, CSGlobal, startpoint, directionvector), + # transform_point( + # i.mid, CSGlobal, startpoint, directionvector), + # transform_point( + # i.end, CSGlobal, startpoint, directionvector) + # )) + # elif i.__class__.__name__ == "Line": + # crvs.append(Line(start=transform_point(i.start, CSGlobal, startpoint, directionvector), + # end=transform_point( + # i.end, CSGlobal, startpoint, directionvector) + # )) + # elif i.__class__.__name__ == "Arc2D": + # # print(Point.point_2D_to_3D(i.start),CSGlobal, startpoint, directionvector) + # crvs.append(Arc(transform_point(Point.point_2D_to_3D(i.start), CSGlobal, startpoint, directionvector), + # transform_point(Point.point_2D_to_3D( + # i.mid), CSGlobal, startpoint, directionvector), + # transform_point(Point.point_2D_to_3D( + # i.end), CSGlobal, startpoint, directionvector) + # )) + # elif i.__class__.__name__ == "Line2D": + # crvs.append(Line(start=transform_point(Point.point_2D_to_3D(i.start), CSGlobal, startpoint, directionvector), + # end=transform_point(Point.point_2D_to_3D( + # i.end), CSGlobal, startpoint, directionvector) + # )) + # else: + # print(i.__class__.__name__ + "Curvetype not found") + + new_polycurve = PolyCurve() + new_polycurve.curves = crvs + new_polycurve.points = pnts + return new_polycurve + + elif polycurve.type == "PolyCurve": + for i in polycurve.curves: + if i.__class__.__name__ == "Arc": + crvs.append(Arc(transform_point(i.start, CSGlobal, startpoint, directionvector), + transform_point( + i.mid, CSGlobal, startpoint, directionvector), + transform_point( + i.end, CSGlobal, startpoint, directionvector) + )) + elif i.__class__.__name__ == "Line": + crvs.append(Line(start=transform_point(i.start, CSGlobal, startpoint, directionvector), + end=transform_point( + i.end, CSGlobal, startpoint, directionvector) + )) + elif i.__class__.__name__ == "Arc2D": + # print(Point.point_2D_to_3D(i.start),CSGlobal, startpoint, directionvector) + crvs.append(Arc(transform_point(Point.point_2D_to_3D(i.start), CSGlobal, startpoint, directionvector), + transform_point(Point.point_2D_to_3D( + i.mid), CSGlobal, startpoint, directionvector), + transform_point(Point.point_2D_to_3D( + i.end), CSGlobal, startpoint, directionvector) + )) + elif i.__class__.__name__ == "Line2D": + crvs.append(Line(start=transform_point(Point.point_2D_to_3D(i.start), CSGlobal, startpoint, directionvector), + end=transform_point(Point.point_2D_to_3D( + i.end), CSGlobal, startpoint, directionvector) + )) + else: + print(i.__class__.__name__ + "Curvetype not found") + + pc = PolyCurve() + pc.curves = crvs + return pc + + def __str__(self) -> 'str': + """Returns a string representation of the PolyCurve. + + #### Returns: + `str`: The string representation of the PolyCurve. + + #### Example usage: + ```python + + ``` + """ + length = len(self.points) + return f"{__class__.__name__}, ({length} points)" + +# 2D PolyCurve to 3D Polygon + + +def Rect(vector: 'Vector3', width: 'float', height: 'float') -> 'PolyCurve': + """Creates a rectangle in the XY-plane with a translation of vector. + + #### Parameters: + - `vector` (Vector3): The translation vector. + - `width` (float): The width of the rectangle. + - `height` (float): The height of the rectangle. + + #### Returns: + `PolyCurve`: The rectangle PolyCurve. + + #### Example usage: + ```python + + ``` + """ + point_1 = Point(0, 0, 0).translate(Point(0, 0, 0), vector) + point_2 = Point(0, 0, 0).translate(Point(width, 0, 0), vector) + point_3 = Point(0, 0, 0).translate(Point(width, height, 0), vector) + point_4 = Point(0, 0, 0).translate(Point(0, height, 0), vector) + crv = PolyCurve.by_points([point_1, point_2, point_3, point_4, point_1]) + return crv + + +def Rect_XY(vector: 'Vector3', width: 'float', height: 'float') -> 'PolyCurve': + """Creates a rectangle in the XY-plane. + + #### Parameters: + - `vector` (Vector3): The base vector of the rectangle. + - `width` (float): The width of the rectangle. + - `height` (float): The height of the rectangle. + + #### Returns: + `PolyCurve`: The rectangle PolyCurve. + + #### Example usage: + ```python + + ``` + """ + point_1 = Point(0, 0, 0).translate(Point(0, 0, 0), vector) + point_2 = Point(0, 0, 0).translate(Point(width, 0, 0), vector) + point_3 = Point(0, 0, 0).translate(Point(width, 0, height), vector) + point_4 = Point(0, 0, 0).translate(Point(0, 0, height), vector) + crv = PolyCurve.by_points([point_1, point_2, point_3, point_4]) + return crv + + +def Rect_YZ(vector: 'Vector3', width: 'float', height: 'float') -> 'PolyCurve': + """Creates a rectangle in the YZ-plane. + + #### Parameters: + - `vector` (Vector3): The base vector of the rectangle. + - `width` (float): The width of the rectangle. + - `height` (float): The height of the rectangle. + + #### Returns: + `PolyCurve`: The rectangle PolyCurve. + + #### Example usage: + ```python + + ``` + """ + point_1 = Point(0, 0, 0).translate(Point(0, 0, 0), vector) + point_2 = Point(0, 0, 0).translate(Point(0, width, 0), vector) + point_3 = Point(0, 0, 0).translate(Point(0, width, height), vector) + point_4 = Point(0, 0, 0).translate(Point(0, 0, height), vector) + crv = PolyCurve.by_points([point_1, point_2, point_3, point_4, point_1]) + return crv + + +class Polygon: + def __init__(self) -> 'Polygon': + """Represents a polygon composed of lines. + + - `lines` (list[Line]): List of lines composing the polygon. + """ + self.id = generateID() + self.type = __class__.__name__ + self.curves = [] + self.points = [] + self.lines = [] + self.isClosed = True + + @classmethod + def by_points(self, points: 'list[Point]') -> 'PolyCurve': + """Creates a Polygon from a list of points. + + #### Parameters: + - `points` (list[Point]): The list of points defining the Polygon. + + #### Returns: + `Polygon`: The created Polygon object. + + #### Example usage: + ```python + + ``` + """ + if len(points) < 3: + print("Error: Polygon must have at least 3 unique points.") + return None + + _points = [] + + for point in points: #Convert all to Point + if point.type == "Point2D": + _points.append(Point(point.x, point.y, 0)) + else: + _points.append(point) + + if Point.to_matrix(_points[0]) == Point.to_matrix(_points[-1]): + _points.pop() + + seen = set() + unique_points = [p for p in _points if not (p in seen or seen.add(p))] + + polygon = Polygon() + polygon.points = unique_points + + num_points = len(unique_points) + for i in range(num_points): + next_index = (i + 1) % num_points + polygon.curves.append(Line(start=unique_points[i], end=unique_points[next_index])) + + return polygon + + + @classmethod + def by_joined_curves(cls, curves: 'list[Line]') -> 'Polygon': + if not curves: + print("Error: At least one curve is required to form a Polygon.") + sys.exit() + + for i in range(len(curves) - 1): + if curves[i].end != curves[i+1].start: + print("Error: Curves must be contiguous to form a Polygon.") + sys.exit() + + if Point.to_matrix(curves[0].start) != Point.to_matrix(curves[-1].end): + curves.append(Line(curves[-1].end, curves[0].start)) + + polygon = cls() + polygon.curves = curves + + for crv in polygon.curves: + if Point.to_matrix(crv.start) not in polygon.points: + polygon.points.append(crv.start) + elif Point.to_matrix(crv.end) not in polygon.points: + polygon.points.append(crv.end) + + return polygon + + + def area(self) -> 'float': # shoelace formula + """Calculates the area enclosed by the Polygon using the shoelace formula. + + #### Returns: + `float`: The area enclosed by the Polygon. + + #### Example usage: + ```python + + ``` + """ + if len(self.points) < 3: + return "Polygon has less than 3 points!" + + num_points = len(self.points) + S1, S2 = 0, 0 + + for i in range(num_points): + x, y = self.points[i].x, self.points[i].y + if i == num_points - 1: + x_next, y_next = self.points[0].x, self.points[0].y + else: + x_next, y_next = self.points[i + 1].x, self.points[i + 1].y + + S1 += x * y_next + S2 += y * x_next + + area = 0.5 * abs(S1 - S2) + return area + + def length(self) -> 'float': + """Calculates the total length of the Polygon. + + #### Returns: + `float`: The total length of the Polygon. + + #### Example usage: + ```python + + ``` + """ + lst = [] + for line in self.curves: + lst.append(line.length) + + return sum(i.length for i in self.curves) + + + def translate(self, vector_3d: 'Vector3') -> 'Polygon': + """Translates the Polygon by a 3D vector. + + #### Parameters: + - `vector_3d` (Vector3): The 3D vector by which to translate the Polygon. + + #### Returns: + `Polygon`: The translated Polygon. + + #### Example usage: + ```python + + ``` + """ + # Create a new Polygon instance to hold the translated polygon + translated_polygon = Polygon() + translated_curves = [] + translated_points = [] + + # Translate all curves + for curve in self.curves: + if curve.__class__.__name__ == "Arc": + # Translate the start, middle, and end points of the arc + new_start = Point.translate(curve.start, vector_3d) + new_middle = Point.translate(curve.middle, vector_3d) + new_end = Point.translate(curve.end, vector_3d) + translated_curves.append(Arc(new_start, new_middle, new_end)) + elif curve.__class__.__name__ == "Line": + # Translate the start and end points of the line + new_start = Point.translate(curve.start, vector_3d) + new_end = Point.translate(curve.end, vector_3d) + translated_curves.append(Line(new_start, new_end)) + else: + print("Curve type not found") + + # Ensure that translated_curves are assigned back to the translated_polygon + translated_polygon.curves = translated_curves + + # Translate all points + for point in self.points: + translated_points.append(Point.translate(point, vector_3d)) + + # Ensure that translated_points are assigned back to the translated_polygon + translated_polygon.points = translated_points + + return translated_polygon + + + def __str__(self) -> str: + return f"{self.type}" + + +class Arc: + def __init__(self, startPoint: 'Point', midPoint: 'Point', endPoint: 'Point') -> 'Arc': + """Initializes an Arc object with start, mid, and end points. + This constructor calculates and assigns the arc's origin, plane, radius, start angle, end angle, angle in radians, area, length, units, and coordinate system based on the input points. + + - `startPoint` (Point): The starting point of the arc. + - `midPoint` (Point): The mid point of the arc which defines its curvature. + - `endPoint` (Point): The ending point of the arc. + """ + self.id = generateID() + self.type = __class__.__name__ + self.start = startPoint + self.mid = midPoint + self.end = endPoint + self.origin = self.origin_arc() + vector_1 = Vector3(x=1, y=0, z=0) + vector_2 = Vector3(x=0, y=1, z=0) + self.plane = Plane.by_two_vectors_origin( + vector_1, + vector_2, + self.origin + ) + self.radius = self.radius_arc() + self.startAngle = 0 + self.endAngle = 0 + self.angle_radian = self.angle_radian() + self.area = 0 + self.length = self.length() + self.units = project.units + self.coordinatesystem = self.coordinatesystem_arc() + + def distance(self, point_1: 'Point', point_2: 'Point') -> float: + """Calculates the Euclidean distance between two points in 3D space. + + #### Parameters: + - `point_1` (Point): The first point. + - `point_2` (Point): The second point. + + #### Returns: + `float`: The Euclidean distance between `point_1` and `point_2`. + + #### Example usage: + ```python + point1 = Point(1, 2, 3) + point2 = Point(4, 5, 6) + distance = arc.distance(point1, point2) + # distance will be the Euclidean distance between point1 and point2 + ``` + """ + return math.sqrt((point_2.x - point_1.x) ** 2 + (point_2.y - point_1.y) ** 2 + (point_2.z - point_1.z) ** 2) + + def coordinatesystem_arc(self) -> 'CoordinateSystem': + """Calculates and returns the coordinate system of the arc. + The coordinate system is defined by the origin of the arc and the normalized vectors along the local X, Y, and Z axes. + + #### Returns: + `CoordinateSystem`: The coordinate system of the arc. + + #### Example usage: + ```python + coordinatesystem = arc.coordinatesystem_arc() + # coordinatesystem will be an instance of CoordinateSystem representing the arc's local coordinate system + ``` + """ + vx = Vector3.by_two_points(self.origin, self.start) # Local X-axe + vector_2 = Vector3.by_two_points(self.end, self.origin) + vz = Vector3.cross_product(vx, vector_2) # Local Z-axe + vy = Vector3.cross_product(vx, vz) # Local Y-axe + self.coordinatesystem = CoordinateSystem(self.origin, Vector3.normalize(vx), Vector3.normalize(vy), + Vector3.normalize(vz)) + return self.coordinatesystem + + def radius_arc(self) -> 'float': + """Calculates and returns the radius of the arc. + The radius is computed based on the distances between the start, mid, and end points of the arc. + + #### Returns: + `float`: The radius of the arc. + + #### Example usage: + ```python + radius = arc.radius_arc() + # radius will be the calculated radius of the arc + ``` + """ + a = self.distance(self.start, self.mid) + b = self.distance(self.mid, self.end) + c = self.distance(self.end, self.start) + s = (a + b + c) / 2 + A = math.sqrt(max(s * (s - a) * (s - b) * (s - c), 0)) + + if abs(A) < 1e-6: + return float('inf') + else: + R = (a * b * c) / (4 * A) + return R + + def origin_arc(self) -> 'Point': + """Calculates and returns the origin of the arc. + The origin is calculated based on the geometric properties of the arc defined by its start, mid, and end points. + + #### Returns: + `Point`: The calculated origin point of the arc. + + #### Example usage: + ```python + origin = arc.origin_arc() + # origin will be the calculated origin point of the arc + ``` + """ + Vstartend = Vector3.by_two_points(self.start, self.end) + halfVstartend = Vector3.scale(Vstartend, 0.5) + b = 0.5 * Vector3.length(Vstartend) + x = math.sqrt(Arc.radius_arc(self) * Arc.radius_arc(self) - b * b) + mid = Point.translate(self.start, halfVstartend) + vector_2 = Vector3.by_two_points(self.mid, mid) + vector_3 = Vector3.normalize(vector_2) + tocenter = Vector3.scale(vector_3, x) + center = Point.translate(mid, tocenter) + return center + + def angle_radian(self) -> 'float': + """Calculates and returns the total angle of the arc in radians. + The angle is determined based on the vectors defined by the start, mid, and end points with respect to the arc's origin. + + #### Returns: + `float`: The total angle of the arc in radians. + + #### Example usage: + ```python + angle = arc.angle_radian() + # angle will be the total angle of the arc in radians + ``` + """ + vector_1 = Vector3.by_two_points(self.origin, self.end) + vector_2 = Vector3.by_two_points(self.origin, self.start) + vector_3 = Vector3.by_two_points(self.origin, self.mid) + vector_4 = Vector3.sum(vector_1, vector_2) + try: + v4b = Vector3.new_length(vector_4, self.radius) + if Vector3.value(vector_3) == Vector3.value(v4b): + angle = Vector3.angle_radian_between(vector_1, vector_2) + else: + angle = 2*math.pi-Vector3.angle_radian_between(vector_1, vector_2) + return angle + except: + angle = 2*math.pi-Vector3.angle_radian_between(vector_1, vector_2) + return angle + + def length(self) -> 'float': + """Calculates and returns the length of the arc. + The length is calculated using the geometric properties of the arc defined by its start, mid, and end points. + + #### Returns: + `float`: The length of the arc. + + #### Example usage: + ```python + length = arc.length() + # length will be the calculated length of the arc + ``` + """ + try: + x1, y1, z1 = self.start.x, self.start.y, self.start.z + x2, y2, z2 = self.mid.x, self.mid.y, self.mid.z + x3, y3, z3 = self.end.x, self.end.y, self.end.z + + r1 = ((x2 - x1) ** 2 + (y2 - y1) ** 2 + (z2 - z1) ** 2) ** 0.5 / 2 + a = math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2 + (z2 - z1) ** 2) + b = math.sqrt((x3 - x2) ** 2 + (y3 - y2) ** 2 + (z3 - z2) ** 2) + c = math.sqrt((x3 - x1) ** 2 + (y3 - y1) ** 2 + (z3 - z1) ** 2) + cos_angle = (a ** 2 + b ** 2 - c ** 2) / (2 * a * b) + m1 = math.acos(cos_angle) + arc_length = r1 * m1 + + return arc_length + except: + return 0 + + @staticmethod + def points_at_parameter(arc: 'Arc', count: 'int') -> 'list': + """Generates a list of points along the arc at specified intervals. + This method divides the arc into segments based on the `count` parameter and calculates points at these intervals along the arc. + + #### Parameters: + - `arc` (Arc): The arc object. + - `count` (int): The number of points to generate along the arc. + + #### Returns: + `list`: A list of points (`Point` objects) along the arc. + + #### Example usage: + ```python + arc = Arc(startPoint, midPoint, endPoint) + points = Arc.points_at_parameter(arc, 5) + # points will be a list of 5 points along the arc + ``` + """ + # Create points at parameter on an arc based on an interval + d_alpha = arc.angle_radian / (count - 1) + alpha = 0 + pnts = [] + for i in range(count): + pnts.append(Point(arc.radius * math.cos(alpha), + arc.radius * math.sin(alpha), 0)) + alpha = alpha + d_alpha + cs_new = arc.coordinatesystem + pnts2 = [] # transformed points + for i in pnts: + pnts2.append(transform_point_2(i, cs_new)) + return pnts2 + + @staticmethod + def segmented_arc(arc: 'Arc', count: 'int') -> 'list': + """Divides the arc into segments and returns a list of line segments. + This method uses the `points_at_parameter` method to generate points along the arc at specified intervals and then creates line segments between these consecutive points. + + #### Parameters: + - `arc` (Arc): The arc object. + - `count` (int): The number of segments (and thus the number of points - 1) to create. + + #### Returns: + `list`: A list of line segments (`Line` objects) representing the divided arc. + + #### Example usage: + ```python + arc = Arc(startPoint, midPoint, endPoint) + segments = Arc.segmented_arc(arc, 3) + # segments will be a list of 2 lines dividing the arc into 3 segments + ``` + """ + pnts = Arc.points_at_parameter(arc, count) + i = 0 + lines = [] + for j in range(len(pnts) - 1): + lines.append(Line(pnts[i], pnts[i + 1])) + i = i + 1 + return lines + + def __str__(self) -> 'str': + """Generates a string representation of the Arc object. + + #### Returns: + `str`: A string that represents the Arc object. + + #### Example usage: + ```python + arc = Arc(startPoint, midPoint, endPoint) + print(arc) + # Output: Arc() + ``` + """ + return f"{__class__.__name__}()" + + +def transform_arc(arc_old, cs_new: 'CoordinateSystem') -> 'Arc': + """Transforms an Arc object to a new coordinate system. + + This function takes an existing Arc object and a new CoordinateSystem object. It transforms the start, mid, and end points of the Arc to the new coordinate system, creating a new Arc object in the process. + + #### Parameters: + - `arc_old` (Arc): The original Arc object to be transformed. + - `cs_new` (CoordinateSystem): The new coordinate system to transform the Arc into. + + #### Returns: + `Arc`: A new Arc object with its points transformed to the new coordinate system. + + #### Example usage: + ```python + arc_old = Arc(startPoint, midPoint, endPoint) + cs_new = CoordinateSystem(...) # Defined elsewhere + arc_new = transform_arc(arc_old, cs_new) + # arc_new is the transformed Arc object + ``` + """ + start = transform_point_2(arc_old.start, cs_new) + mid = transform_point_2(arc_old.mid, cs_new) + end = transform_point_2(arc_old.end, cs_new) + arc_new = Arc(startPoint=start, midPoint=mid, endPoint=end) + + return arc_new + + +class Circle: + """Represents a circle with a specific radius, plane, and length. + """ + def __init__(self, radius: 'float', plane: 'Plane', length: 'float') -> 'Circle': + """The Circle class defines a circle by its radius, the plane it lies in, and its calculated length (circumference). + + - `radius` (float): The radius of the circle. + - `plane` (Plane): The plane in which the circle lies. + - `length` (float): The length (circumference) of the circle. Automatically calculated during initialization. + """ + self.type = __class__.__name__ + self.radius = radius + self.plane = plane + self.length = length + self.id = generateID() + pass # Curve + + def __id__(self): + """Returns the ID of the Circle. + + #### Returns: + `str`: The ID of the Circle in the format "id:{self.id}". + """ + + def __str__(self) -> 'str': + """Generates a string representation of the Circle object. + + #### Returns: + `str`: A string that represents the Circle object. + + #### Example usage: + ```python + circle = Circle(radius, plane, length) + print(circle) + # Output: Circle(...) + ``` + """ + + +class Ellipse: + """Represents an ellipse defined by its two radii and the plane it lies in.""" + def __init__(self, firstRadius: 'float', secondRadius: 'float', plane: 'Plane') -> 'Ellipse': + """The Ellipse class describes an ellipse through its major and minor radii and the plane it occupies. + + - `firstRadius` (float): The first (major) radius of the ellipse. + - `secondRadius` (float): The second (minor) radius of the ellipse. + - `plane` (Plane): The plane in which the ellipse lies. + """ + self.type = __class__.__name__ + self.firstRadius = firstRadius + self.secondRadius = secondRadius + self.plane = plane + self.id = generateID() + pass # Curve + + def __id__(self): + """Returns the ID of the Ellipse. + + #### Returns: + `str`: The ID of the Ellipse in the format "id:{self.id}". + """ + return f"id:{self.id}" + + def __str__(self) -> 'str': + """Generates a string representation of the Ellipse object. + + #### Returns: + `str`: A string that represents the Ellipse object. + + #### Example usage: + ```python + ellipse = Ellipse(firstRadius, secondRadius, plane) + print(ellipse) + # Output: Ellipse(...) + ``` + """ + return f"{__class__.__name__}({self})" +# check if there are innercurves inside the outer curve. + + +class Surface: + """Represents a surface object created from PolyCurves.""" + def __init__(self) -> 'Surface': + """This class is designed to manage and manipulate surfaces derived from PolyCurve objects. It supports the generation of mesh representations, serialization/deserialization, and operations like filling and voiding based on PolyCurve inputs. + + - `type` (str): The class name, "Surface". + - `mesh` (list): A list of meshes that represent the surface. + - `length` (float): The total length of the PolyCurves defining the surface. + - `area` (float): The area of the surface, excluding any inner PolyCurves. + - `offset` (float): An offset value for the surface. + - `name` (str): The name of the surface. + - `id` (str): A unique identifier for the surface. + - `PolyCurveList` (list): A list of PolyCurve objects that define the surface. + - `origincurve` (PolyCurve): The original PolyCurve from which the surface was created. + - `color` (int): The color of the surface, represented as an integer. + - `colorlst` (list): A list of color values associated with the surface. + """ + self.type = __class__.__name__ + self.mesh = [] + self.offset = 0 + self.name = None + self.id = generateID() + self.outer_Polygon = None + self.inner_Polygon = [] + self.colorlst = [] + self.outer_Surface = None + self.inner_Surface = [] + # self.byPatch = self.fill(self) + # if color is None: + # self.color = Color.rgb_to_int(Color().Components("gray")) + # else: + # self.color = color + + + + def serialize(self) -> dict: + """Serializes the Surface object into a dictionary for storage or transfer. + This method converts the Surface object's properties into a dictionary format, making it suitable for serialization processes like saving to a file or sending over a network. + + #### Returns: + `dict`: A dictionary representation of the Surface object, containing all relevant data such as type, mesh, dimensions, name, ID, PolyCurve list, origin curve, color, and color list. + + #### Example usage: + ```python + surface = Surface(polyCurves, color) + serialized_surface = surface.serialize() + # serialized_surface is now a dictionary representation of the surface object + ``` + """ + return { + 'type': self.type, + 'mesh': self.mesh, + 'length': self.length, + 'area': self.area, + 'offset': self.offset, + 'name': self.name, + 'id': self.id, + 'PolyCurveList': [polycurve.serialize() for polycurve in self.PolyCurveList], + 'origincurve': self.origincurve.serialize() if self.origincurve else None, + 'color': self.color, + 'colorlst': self.colorlst + } + + @staticmethod + def deserialize(data: dict) -> 'Surface': + """Creates a Surface object from a serialized data dictionary. + This static method reconstructs a Surface object from a dictionary containing serialized surface data. It is particularly useful for loading surfaces from storage or reconstructing them from data received over a network. + + #### Parameters: + - `data` (`dict`): The dictionary containing the serialized data of a Surface object. + + #### Returns: + `Surface`: A new Surface object initialized with the data from the dictionary. + + #### Example usage: + ```python + data = { ... } # Serialized Surface data + surface = Surface.deserialize(data) + # surface is now a fully reconstructed Surface object + ``` + """ + polycurves = [PolyCurve.deserialize( + pc_data) for pc_data in data.get('PolyCurveList', [])] + surface = Surface(polycurves, data.get('color')) + + surface.mesh = data.get('mesh', []) + surface.length = data.get('length', 0) + surface.area = data.get('area', 0) + surface.offset = data.get('offset', 0) + surface.name = data.get('name', "test2") + surface.id = data.get('id') + surface.colorlst = data.get('colorlst', []) + + if data.get('origincurve'): + surface.origincurve = PolyCurve.deserialize(data['origincurve']) + + return surface + @classmethod + def by_patch_inner_and_outer(self, Polygons: 'list[Polygon]') -> 'Surface': + valid_polygons = [p for p in Polygons if p is not None] + sorted_polygons = sorted(valid_polygons, key=lambda p: p.length(), reverse=True) + + if len(sorted_polygons) == 0: + raise ValueError("No valid polygons provided") + + outer_Polygon = sorted_polygons[0] + + inner_Polygon = sorted_polygons[1:] if len(sorted_polygons) > 1 else [] + + return self.by_patch(outer_Polygon, inner_Polygon) + + + @classmethod + def by_patch(self, outer_Polygon: Polygon, inner_Polygon: 'list[Polygon]' = None) -> 'Surface': + srf = Surface() + srf.outer_Polygon = outer_Polygon + srf.inner_Polygon = inner_Polygon + srf.outer_Surface = Extrusion.by_polycurve_height(outer_Polygon, 0, 0) + srf.inner_Surface = [] + if inner_Polygon != None: + for inner in srf.inner_Polygon: + srf.inner_Surface.append(Extrusion.by_polycurve_height(inner, 0, 0)) + + return srf + + def void(self, polyCurve: PolyCurve): + """Creates a void in the Surface based on the specified PolyCurve. + This method identifies and removes a part of the Surface that intersects with the given PolyCurve, effectively creating a void in the Surface. It then updates the surface's mesh and color list to reflect this change. + + #### Parameters: + - `polyCurve` (`PolyCurve`): The PolyCurve object that defines the area of the Surface to be voided. + + #### Example usage: + ```python + surface.void(polyCurve) + # A void is now created in the surface based on the specified PolyCurve. + ``` + """ + # Find the index of the extrusion that intersects with the polyCurve + pass + + def __id__(self): + """Returns the unique identifier of the Surface. + This method provides a way to retrieve the unique ID of the Surface, which can be useful for tracking or identifying surfaces within a larger system. + + #### Returns: + `str`: The unique identifier of the Surface. + + #### Example usage: + ```python + id_str = surface.__id__() + print(id_str) + # Outputs the ID of the surface. + ``` + """ + + def __str__(self) -> str: + return f"{self.__class__.__name__}({self.outer_Polygon}, {self.inner_Polygon})" + + + +class NurbsSurface: # based on point data / degreeU&countU / degreeV&countV? + """Represents a NURBS (Non-Uniform Rational B-Spline) surface.""" + def __init__(self) -> 'NurbsSurface': + """NurbsSurface is a mathematical model representing a 3D surface in terms of NURBS, a flexible method to represent curves and surfaces. It encompasses properties such as ID and type but is primarily defined by its control points, weights, and degree in the U and V directions. + + - `id` (str): A unique identifier for the NurbsSurface. + - `type` (str): Class name, "NurbsSurface". + """ + self.id = generateID() + self.type = __class__.__name__ + + def __id__(self) -> 'str': + """Returns the unique identifier of the NurbsSurface object. + This method provides a standardized way to access the unique ID of the NurbsSurface, useful for identification and tracking purposes within a system that handles multiple surfaces. + + #### Returns: + `str`: The unique identifier of the NurbsSurface, prefixed with "id:". + + #### Example usage: + ```python + nurbs_surface = NurbsSurface() + print(nurbs_surface.__id__()) + # Output format: "id:{unique_id}" + ``` + """ + return f"id:{self.id}" + + def __str__(self) -> 'str': + """Generates a string representation of the NurbsSurface object. + This method creates a string that summarizes the NurbsSurface, typically including its class name and potentially its unique ID, providing a concise overview of the object when printed or logged. + + #### Returns: + `str`: A string representation of the NurbsSurface object. + + #### Example usage: + ```python + nurbs_surface = NurbsSurface() + print(nurbs_surface) + # Output: "NurbsSurface({self})" + ``` + """ + return f"{__class__.__name__}({self})" + + +class PolySurface: + """Represents a compound surface consisting of multiple connected surfaces.""" + def __init__(self) -> None: + """PolySurface is a geometric entity that represents a complex surface made up of several simpler surfaces. These simpler surfaces are typically connected along their edges. Attributes include an ID and type, with functionalities to manipulate and query the composite surface structure. + + - `id` (str): A unique identifier for the PolySurface. + - `type` (str): Class name, "PolySurface". + """ + self.id = generateID() + self.type = __class__.__name__ + + def __id__(self) -> 'str': + """Returns the unique identifier of the PolySurface object. + Similar to the NurbsSurface, this method provides the unique ID of the PolySurface, facilitating its identification and tracking across various operations or within data structures that involve multiple surfaces. + + #### Returns: + `str`: The unique identifier of the PolySurface, prefixed with "id:". + + #### Example usage: + ```python + poly_surface = PolySurface() + print(poly_surface.__id__()) + # Output format: "id:{unique_id}" + ``` + """ + return f"id:{self.id}" + + def __str__(self) -> 'str': + """Generates a string representation of the PolySurface object. + Provides a simple string that identifies the PolySurface, mainly through its class name. This is helpful for debugging, logging, or any scenario where a quick textual representation of the object is beneficial. + + #### Returns: + `str`: A string representation of the PolySurface object. + + #### Example usage: + ```python + poly_surface = PolySurface() + print(poly_surface) + # Output: "PolySurface({self})" + ``` + """ + return f"{__class__.__name__}({self})" + + +class Extrusion: + # Extrude a 2D profile to a 3D mesh or solid + """The Extrusion class represents the process of extruding a 2D profile into a 3D mesh or solid form. It is designed to handle geometric transformations and properties related to the extrusion process.""" + def __init__(self): + """The Extrusion class represents the process of extruding a 2D profile into a 3D mesh or solid form. It is designed to handle geometric transformations and properties related to the extrusion process. + + - `id` (str): A unique identifier for the extrusion instance. + - `type` (str): Class name, indicating the object type as "Extrusion". + - `parameters` (list): A list of parameters associated with the extrusion. + - `verts` (list): A list of vertices that define the shape of the extruded mesh. + - `faces` (list): A list of faces, each defined by indices into the `verts` list. + - `numberFaces` (int): The total number of faces in the extrusion. + - `countVertsFaces` (int): The total number of vertices per face, distinct from the total vertex count. + - `name` (str): The name assigned to the extrusion instance. + - `color` (tuple): The color of the extrusion, defined as an RGB tuple. + - `colorlst` (list): A list of colors applied to the extrusion, potentially varying per face or vertex. + - `topface` (PolyCurve): The top face of the extrusion, returned as a polycurve converted to a surface. + - `bottomface` (PolyCurve): The bottom face of the extrusion, similar to `topface`. + - `polycurve_3d_translated` (PolyCurve): A polycurve representing the translated 3D profile of the extrusion. + - `bottomshape` (list): A list representing the shape of the bottom face of the extrusion. + """ + self.id = generateID() + self.type = __class__.__name__ + self.parameters = [] + self.verts = [] + self.faces = [] + self.numberFaces = 0 + # total number of verts per face (not the same as total verts) + self.countVertsFaces = 0 + self.name = None + self.color = (255, 255, 0) + self.colorlst = [] + self.topface = None # return polycurve -> surface + self.bottomface = None # return polycurve -> surface + self.polycurve_3d_translated = None + self.outercurve = [] + self.bottomshape = [] + self.nested = [] + + def serialize(self) -> dict: + """Serializes the extrusion object into a dictionary. + This method facilitates the conversion of the Extrusion instance into a dictionary format, suitable for serialization to JSON or other data formats for storage or network transmission. + + #### Returns: + `dict`: A dictionary representation of the Extrusion instance, including all relevant geometric and property data. + + #### Example usage: + ```python + + ``` + """ + id_value = str(self.id) if not isinstance( + self.id, (str, int, float)) else self.id + return { + 'id': id_value, + 'type': self.type, + 'verts': self.verts, + 'faces': self.faces, + 'numberFaces': self.numberFaces, + 'countVertsFaces': self.countVertsFaces, + 'name': self.name, + 'color': self.color, + 'colorlst': self.colorlst, + 'topface': self.topface.serialize() if self.topface else None, + 'bottomface': self.bottomface.serialize() if self.bottomface else None, + 'polycurve_3d_translated': self.polycurve_3d_translated.serialize() if self.polycurve_3d_translated else None + } + + @staticmethod + def deserialize(data: dict) -> 'Extrusion': + """Reconstructs an Extrusion object from a dictionary. + This static method allows for the creation of an Extrusion instance from serialized data, enabling the loading of extrusion objects from file storage or network data. + + #### Parameters: + - `data` (dict): A dictionary containing serialized Extrusion data. + + #### Returns: + `Extrusion`: A newly constructed Extrusion instance based on the provided data. + + #### Example usage: + ```python + + ``` + """ + extrusion = Extrusion() + extrusion.id = data.get('id') + extrusion.verts = data.get('verts', []) + extrusion.faces = data.get('faces', []) + extrusion.numberFaces = data.get('numberFaces', 0) + extrusion.countVertsFaces = data.get('countVertsFaces', 0) + extrusion.name = data.get('name', "none") + extrusion.color = data.get('color', (255, 255, 0)) + extrusion.colorlst = data.get('colorlst', []) + + if data.get('topface'): + extrusion.topface = PolyCurve.deserialize(data['topface']) + + if data.get('bottomface'): + extrusion.bottomface = PolyCurve.deserialize(data['bottomface']) + + if data.get('polycurve_3d_translated'): + extrusion.polycurve_3d_translated = PolyCurve.deserialize( + data['polycurve_3d_translated']) + + return extrusion + + def set_parameter(self, data: list) -> 'Extrusion': + """Sets parameters for the extrusion. + This method allows for the modification of the Extrusion's parameters, which can influence the extrusion process or define additional properties. + + #### Parameters: + - `data` (list): A list of parameters to be applied to the extrusion. + + #### Returns: + `Extrusion`: The Extrusion instance with updated parameters. + + #### Example usage: + ```python + + ``` + """ + self.parameters = data + return self + + @staticmethod + def merge(extrusions: list, name: str = None) -> 'Extrusion': + """Merges multiple Extrusion instances into a single one. + This class method combines several extrusions into a single Extrusion object, potentially useful for operations requiring unified geometric manipulation. + + #### Parameters: + - `extrusions` (list): A list of Extrusion instances to be merged. + - `name` (str, optional): The name for the merged extrusion. + + #### Returns: + `Extrusion`: A new Extrusion instance resulting from the merger of the provided extrusions. + + #### Example usage: + ```python + + ``` + """ + Outrus = Extrusion() + if isinstance(extrusions, list): + Outrus.verts = [] + Outrus.faces = [] + Outrus.colorlst = [] + for ext in extrusions: + Outrus.verts.append(ext.verts) + Outrus.faces.append(ext.faces) + Outrus.colorlst.append(ext.colorlst) + Outrus.verts = flatten(Outrus.verts) + Outrus.faces = flatten(Outrus.faces) + Outrus.colorlst = flatten(Outrus.colorlst) + return Outrus + + elif isinstance(extrusions, Extrusion): + return extrusions + + @staticmethod + def by_polycurve_height_vector(polycurve_2d: PolyCurve2D, height: float, cs_old: CoordinateSystem, start_point: Point, direction_vector: Vector3) -> 'Extrusion': + """Creates an extrusion from a 2D polycurve profile along a specified vector. + This method extrudes a 2D polycurve profile into a 3D form by translating it to a specified start point and direction. The extrusion is created perpendicular to the polycurve's plane, extending it to the specified height. + + #### Parameters: + - `polycurve_2d` (PolyCurve2D): The 2D polycurve to be extruded. + - `height` (float): The height of the extrusion. + - `cs_old` (CoordinateSystem): The original coordinate system of the polycurve. + - `start_point` (Point): The start point for the extrusion in the new coordinate system. + - `direction_vector` (Vector3): The direction vector along which the polycurve is extruded. + + #### Returns: + `Extrusion`: An Extrusion object representing the 3D form of the extruded polycurve. + + #### Example usage: + ```python + extrusion = Extrusion.by_polycurve_height_vector(polycurve_2d, 10, oldCS, startPoint, directionVec) + ``` + """ + Extrus = Extrusion() + # 2D PolyCurve @ Global origin + count = 0 + + Extrus.polycurve_3d_translated = PolyCurve.transform_from_origin( + polycurve_2d, start_point, direction_vector) + + try: + for i in polycurve_2d.curves: + startpointLow = transform_point( + Point(i.start.x, i.start.y, 0), cs_old, start_point, direction_vector) + endpointLow = transform_point( + Point(i.end.x, i.end.y, 0), cs_old, start_point, direction_vector) + endpointHigh = transform_point( + Point(i.end.x, i.end.y, height), cs_old, start_point, direction_vector) + startpointHigh = transform_point( + Point(i.start.x, i.start.y, height), cs_old, start_point, direction_vector) + + # Construct faces perpendicular on polycurve + Extrus.faces.append(4) + Extrus.verts.append(startpointLow.x) + Extrus.verts.append(startpointLow.y) + Extrus.verts.append(startpointLow.z) + Extrus.faces.append(count) + count += 1 + Extrus.verts.append(endpointLow.x) + Extrus.verts.append(endpointLow.y) + Extrus.verts.append(endpointLow.z) + Extrus.faces.append(count) + count += 1 + Extrus.verts.append(endpointHigh.x) + Extrus.verts.append(endpointHigh.y) + Extrus.verts.append(endpointHigh.z) + Extrus.faces.append(count) + count += 1 + Extrus.verts.append(startpointHigh.x) + Extrus.verts.append(startpointHigh.y) + Extrus.verts.append(startpointHigh.z) + Extrus.faces.append(count) + count += 1 + Extrus.numberFaces = Extrus.numberFaces + 1 + + # bottomface + Extrus.faces.append(len(polycurve_2d.curves)) + + count = 0 + for i in polycurve_2d.curves: + Extrus.faces.append(count) + Extrus.bottomshape.append(i) + count = count + 4 + + # topface + Extrus.faces.append(len(polycurve_2d.curves)) + count = 3 + for i in polycurve_2d.curves: + Extrus.faces.append(count) + count = count + 4 + except: + for i in polycurve_2d.curves: + startpointLow = transform_point( + Point(i.start.x, i.start.y, 0), cs_old, start_point, direction_vector) + endpointLow = transform_point( + Point(i.end.x, i.end.y, 0), cs_old, start_point, direction_vector) + endpointHigh = transform_point( + Point(i.end.x, i.end.y, height), cs_old, start_point, direction_vector) + startpointHigh = transform_point( + Point(i.start.x, i.start.y, height), cs_old, start_point, direction_vector) + + # Construct faces perpendicular on polycurve + Extrus.faces.append(4) + Extrus.verts.append(startpointLow.x) + Extrus.verts.append(startpointLow.y) + Extrus.verts.append(startpointLow.z) + Extrus.faces.append(count) + count += 1 + Extrus.verts.append(endpointLow.x) + Extrus.verts.append(endpointLow.y) + Extrus.verts.append(endpointLow.z) + Extrus.faces.append(count) + count += 1 + Extrus.verts.append(endpointHigh.x) + Extrus.verts.append(endpointHigh.y) + Extrus.verts.append(endpointHigh.z) + Extrus.faces.append(count) + count += 1 + Extrus.verts.append(startpointHigh.x) + Extrus.verts.append(startpointHigh.y) + Extrus.verts.append(startpointHigh.z) + Extrus.faces.append(count) + count += 1 + Extrus.numberFaces = Extrus.numberFaces + 1 + + # bottomface + Extrus.faces.append(len(polycurve_2d.curves)) + + count = 0 + for i in polycurve_2d.curves: + Extrus.faces.append(count) + Extrus.bottomshape.append(i) + count = count + 4 + + # topface + Extrus.faces.append(len(polycurve_2d.curves)) + count = 3 + for i in polycurve_2d.curves: + Extrus.faces.append(count) + count = count + 4 + + Extrus.countVertsFaces = (4 * Extrus.numberFaces) + + Extrus.countVertsFaces = Extrus.countVertsFaces + \ + len(polycurve_2d.curves)*2 + Extrus.numberFaces = Extrus.numberFaces + 2 + + Extrus.outercurve = polycurve_2d + + for j in range(int(len(Extrus.verts) / 3)): + Extrus.colorlst.append(Extrus.color) + + return Extrus + + @staticmethod + def by_polycurve_height(polycurve: PolyCurve, height: float, dz_loc: float) -> 'Extrusion': + """Creates an extrusion from a PolyCurve with a specified height and base elevation. + This method generates a vertical extrusion of a given PolyCurve. The PolyCurve is first translated vertically by `dz_loc`, then extruded to the specified `height`, creating a solid form. + + #### Parameters: + - `polycurve` (PolyCurve): The PolyCurve to be extruded. + - `height` (float): The height of the extrusion. + - `dz_loc` (float): The base elevation offset from the original plane of the PolyCurve. + + #### Returns: + `Extrusion`: An Extrusion object that represents the 3D extruded form of the input PolyCurve. + + #### Example usage: + ```python + extrusion = Extrusion.by_polycurve_height(polycurve, 5, 0) + ``` + """ + # global len + Extrus = Extrusion() + Points = polycurve.points + V1 = Vector3.by_two_points(Points[0], Points[1]) + V2 = Vector3.by_two_points(Points[-2], Points[-1]) + + p1 = Plane.by_two_vectors_origin( + V1, V2, Points[0]) # Workplane of PolyCurve + norm = p1.Normal + + pnts = [] + faces = [] + + Extrus.polycurve_3d_translated = polycurve + + # allverts + for pnt in Points: + # Onderzijde verplaatst met dz_loc + pnts.append(Point.translate(pnt, Vector3.product(dz_loc, norm))) + for pnt in Points: + # Bovenzijde verplaatst met dz_loc + pnts.append(Point.translate( + pnt, Vector3.product((dz_loc+height), norm))) + + numPoints = len(Points) + + # Bottomface + count = 0 + face = [] + for x in range(numPoints): + face.append(count) + count = count + 1 + faces.append(face) + + # Topface + count = 0 + face = [] + for x in range(numPoints): + face.append(count+numPoints) + count = count + 1 + faces.append(face) + + # Sides + count = 0 + length = len(faces[0]) + for i, j in zip(faces[0], faces[1]): + face = [] + face.append(i) + face.append(faces[0][count + 1]) + face.append(faces[1][count + 1]) + face.append(j) + count = count + 1 + if count == length-1: + face.append(i) + face.append(faces[0][0]) + face.append(faces[1][0]) + face.append(j) + faces.append(face) + break + else: + pass + faces.append(face) + + # toMeshStructure + for i in pnts: + Extrus.verts.append(i.x) + Extrus.verts.append(i.y) + Extrus.verts.append(i.z) + + for x in faces: + Extrus.faces.append(len(x)) # Number of verts in face + for y in x: + Extrus.faces.append(y) + + Extrus.numberFaces = len(faces) + Extrus.countVertsFaces = (4 * len(faces)) + + for j in range(int(len(Extrus.verts) / 3)): + Extrus.colorlst.append(Extrus.color) + return Extrus + + +class System: + """Represents a generic system with a defined direction.""" + def __init__(self): + """Initializes a new System instance. + + - `type` (str): The class name, indicating the object type as "System". + - `name` (str, optional): The name of the system. + - `id` (str): A unique identifier for the system instance. + - `polycurve` (PolyCurve, optional): An optional PolyCurve associated with the system. + - `direction` (Vector3): A Vector3 indicating the primary direction of the system. + """ + self.type = __class__.__name__ + self.name = None + self.id = generateID() + self.polycurve = None + self.direction: Vector3 = Vector3(1, 0, 0) + + +class DivisionSystem: + # This class provides divisionsystems. It returns lists with floats based on a length. + """The `DivisionSystem` class manages division systems, providing functionalities to calculate divisions and spacings based on various criteria.""" + def __init__(self): + """Initializes a new DivisionSystem instance. + + - `type` (str): The class name, "DivisionSystem". + - `name` (str): The name of the division system. + - `id` (str): A unique identifier for the division system instance. + - `system_length` (float): The total length of the system to be divided. + - `spacing` (float): The spacing between divisions. + - `distance_first` (float): The distance of the first division from the start of the system. + - `width_stud` (float): The width of a stud, applicable in certain division strategies. + - `fixed_number` (int): A fixed number of divisions. + - `modifier` (int): A modifier value that adjusts the number of divisions or their placement. + - `distances` (list): A list containing the cumulative distances of each division from the start. + - `spaces` (list): A list containing the spaces between each division. + - `system` (str): A string indicating the current system strategy (e.g., "fixed_distance_unequal_division"). + """ + self.type = __class__.__name__ + self.name = None + self.id = generateID() + self.system_length: float = 100 + self.spacing: float = 10 + self.distance_first: float = 5 + self.width_stud: float = 10 + self.fixed_number: int = 2 + self.modifier: int = 0 + self.distances = [] # List with sum of distances + self.spaces = [] # List with spaces between every divison + self.system: str = "fixed_distance_unequal_division" + + def __fixed_number_equal_spacing(self): + """Calculates divisions based on a fixed number with equal spacing. + This internal method sets up divisions across the system length, ensuring each division is equally spaced. It is triggered by configurations that require a fixed number of divisions, automatically adjusting the spacing to fit the total length. + + #### Effects: + - Sets the division system name to "fixed_number_equal_spacing". + - Calculates equal spacing between divisions based on the total system length and the fixed number of divisions. + - Resets the modifier to 0, as it is not applicable in this configuration. + - Assigns the calculated spacing to `distance_first` to maintain consistency at the start of the system. + """ + self.name = "fixed_number_equal_spacing" + self.distances = Interval.by_start_end_count( + 0, self.system_length, self.fixed_number) + self.spacing = self.system_length / self.fixed_number + self.modifier = 0 + self.distance_first = self.spacing + + def __fixed_distance_unequal_division(self): + """Configures divisions with a fixed starting distance followed by unequal divisions. + This internal method configures the division system to start with a specified distance for the first division, then continues with divisions spaced according to `spacing`. If the total length cannot be evenly divided, the last division's spacing may differ. + + #### Effects: + - Sets the division system name to "fixed_distance_unequal_division". + - Calculates the number of divisions based on the spacing and the total system length minus the first division's distance. + - Generates a list of distances where each division should occur, considering the initial distance and spacing. + """ + self.name = "fixed_distance_unequal_division" + rest_length = self.system_length - self.distance_first + number_of_studs = int(rest_length / self.spacing) + number_of_studs = number_of_studs + self.modifier + distance = self.distance_first + for i in range(number_of_studs+1): + if distance < self.system_length: + self.distances.append(distance) + else: + break + distance = distance + self.spacing + + def __fixed_distance_equal_division(self): + """Creates divisions with equal spacing across the total system length. + An internal method that evenly distributes divisions across the system's length. It takes into account the total length and the desired spacing to calculate the number of divisions, ensuring they are equally spaced. + + #### Effects: + - Sets the division system name to "fixed_distance_equal_division". + - Calculates the number of divisions based on the desired spacing and total length. + - Determines the starting distance for the first division to ensure all divisions, including the first and last, are equally spaced within the system length. + """ + self.name = "fixed_distance_equal_division" + number_of_studs = int(self.system_length / self.spacing) + number_of_studs = number_of_studs + self.modifier + sum_length_studs_x_spacing = (number_of_studs - 1) * self.spacing + rest_length = self.system_length - sum_length_studs_x_spacing + distance = rest_length / 2 + for i in range(number_of_studs): + self.distances.append(distance) + distance = distance + self.spacing + + def by_fixed_distance_unequal_division(self, length: float, spacing: float, distance_first: float, modifier: int) -> 'DivisionSystem': + """Configures the division system for unequal divisions with a specified distance first. + This method sets up the division system to calculate divisions based on a fixed initial distance, followed by unevenly spaced divisions according to the specified parameters. + + #### Parameters: + - `length` (float): The total length of the system to be divided. + - `spacing` (float): The target spacing between divisions. + - `distance_first` (float): The distance of the first division from the system's start. + - `modifier` (int): An integer modifier to adjust the calculation of divisions. + + #### Returns: + `DivisionSystem`: The instance itself, updated with the new division configuration. + + #### Example usage: + ```python + division_system = DivisionSystem() + division_system.by_fixed_distance_unequal_division(100, 10, 5, 0) + ``` + """ + self.system_length = length + self.modifier = modifier + self.spacing = spacing + self.distance_first = distance_first + self.system = "fixed_distance_unequal_division" + self.__fixed_distance_unequal_division() + return self + + def by_fixed_distance_equal_division(self, length: float, spacing: float, modifier: int) -> 'DivisionSystem': + """Configures the division system for equal divisions with fixed spacing. + This method sets up the division system to calculate divisions based on a fixed spacing between each division across the total system length. The modifier can adjust the calculation slightly but maintains equal spacing. + + #### Parameters: + - `length` (float): The total length of the system to be divided. + - `spacing` (float): The spacing between each division. + - `modifier` (int): An integer modifier to fine-tune the division process. + + #### Returns: + `DivisionSystem`: The instance itself, updated with the new division configuration. + + #### Example usage: + ```python + division_system = DivisionSystem() + division_system.by_fixed_distance_equal_division(100, 10, 0) + ``` + """ + self.system_length = length + self.modifier = modifier + self.spacing = spacing + self.system = "fixed_distance_equal_division" + self.__fixed_distance_equal_division() + return self + + def by_fixed_number_equal_spacing(self, length: float, number: int) -> 'DivisionSystem': + """Establishes the division system for a fixed number of divisions with equal spacing. + This method arranges for a certain number of divisions to be spaced equally across the system length. It calculates the required spacing based on the total length and desired number of divisions. + + #### Parameters: + - `length` (float): The total length of the system to be divided. + - `number` (int): The fixed number of divisions to be created. + + #### Returns: + `DivisionSystem`: The instance itself, updated with the new division configuration. + + #### Example usage: + ```python + division_system = DivisionSystem() + division_system.by_fixed_number_equal_spacing(100, 5) + ``` + """ + self.system_length = length + self.system = "fixed_number_equal_spacing" + self.spacing = length/number + self.modifier = 0 + distance = self.spacing + for i in range(number-1): + self.distances.append(distance) + distance = distance + self.spacing + self.distance_first = self.spacing + return self + + # fixed_number_equal_interior_fill + # maximum_spacing_equal_division + # maximum_spacing_unequal_division + # minimum_spacing_equal_division + # minimum_spacing_unequal_division + + +class RectangleSystem: + # Reclangle Left Bottom is in Local XYZ. Main direction parallel to height direction vector. Top is z=0 + """The `RectangleSystem` class is designed to represent and manipulate rectangular systems, focusing on dimensions, frame types, and panel arrangements within a specified coordinate system.""" + def __init__(self): + """Initializes a new RectangleSystem instance. + + - `type` (str): Class name, indicating the object type as "RectangleSystem". + - `name` (str, optional): The name of the rectangle system. + - `id` (str): A unique identifier for the rectangle system instance. + - `height` (float): The height of the rectangle system. + - `width` (float): The width of the rectangle system. + - `bottom_frame_type` (Rectangle): A `Rectangle` instance for the bottom frame type. + - `top_frame_type` (Rectangle): A `Rectangle` instance for the top frame type. + - `left_frame_type` (Rectangle): A `Rectangle` instance for the left frame type. + - `right_frame_type` (Rectangle): A `Rectangle` instance for the right frame type. + - `inner_frame_type` (Rectangle): A `Rectangle` instance for the inner frame type. + - `material` (BaseTimber): The material used for the system, pre-defined as `BaseTimber`. + - `inner_width` (float): The computed inner width of the rectangle system, excluding the width of the left and right frames. + - `inner_height` (float): The computed inner height of the rectangle system, excluding the height of the top and bottom frames. + - `coordinatesystem` (CSGlobal): A global coordinate system applied to the rectangle system. + - `local_coordinate_system` (CSGlobal): A local coordinate system specific to the rectangle system. + - `division_system` (DivisionSystem, optional): A `DivisionSystem` instance to manage divisions within the rectangle system. + - `inner_frame_objects` (list): A list of inner frame objects within the rectangle system. + - `outer_frame_objects` (list): A list of outer frame objects. + - `panel_objects` (list): A list of panel objects used within the system. + - `symbolic_inner_mother_surface` (PolyCurve, optional): A symbolic representation of the inner mother surface. + - `symbolic_inner_panels` (list, optional): Symbolic representations of inner panels. + - `symbolic_outer_grids` (list): Symbolic representations of outer grids. + - `symbolic_inner_grids` (list): Symbolic representations of inner grids. + """ + self.type = __class__.__name__ + self.name = None + self.id = generateID() + self.height = 3000 + self.width = 2000 + self.bottom_frame_type = Rectangle("bottom_frame_type", 38, 184) + self.top_frame_type = Rectangle("top_frame_type", 38, 184) + self.left_frame_type = Rectangle("left_frame_type", 38, 184) + self.right_frame_type = Rectangle("left_frame_type", 38, 184) + self.inner_frame_type = Rectangle("inner_frame_type", 38, 184) + + self.material = BaseTimber + self.inner_width: float = 0 + self.inner_height: float = 0 + self.coordinatesystem = CSGlobal + self.local_coordinate_system = CSGlobal + # self.openings = [] + # self.subsystems = [] + self.division_system = None + self.inner_frame_objects = [] + self.outer_frame_objects = [] + self.panel_objects = [] + self.symbolic_inner_mother_surface = None + self.symbolic_inner_panels = None + self.symbolic_outer_grids = [] + self.symbolic_inner_grids = [] + + def __inner_panels(self): + """Calculates and creates inner panel objects for the RectangleSystem. + This method iteratively calculates the positions and dimensions of inner panels based on the division system's distances and the inner frame type's width. It populates the `panel_objects` list with created panels. + + #### Effects: + - Populates `panel_objects` with Panel instances representing the inner panels of the rectangle system. + """ + # First Inner panel + i = self.division_system.distances[0] + point1 = self.mother_surface_origin_point_x_zero + point2 = Point.translate(self.mother_surface_origin_point_x_zero, Vector3( + i - self.inner_frame_type.b * 0.5, 0, 0)) + point3 = Point.translate(self.mother_surface_origin_point_x_zero, + Vector3(i - self.inner_frame_type.b * 0.5, self.inner_height, 0)) + point4 = Point.translate( + self.mother_surface_origin_point_x_zero, Vector3(0, self.inner_height, 0)) + self.panel_objects.append( + Panel.by_polycurve_thickness( + PolyCurve.by_points( + [point1, point2, point3, point4, point1]), 184, 0, "innerpanel", + rgb_to_int([255, 240, 160])) + ) + count = 0 + # In between + for i in self.division_system.distances: + try: + point1 = Point.translate(self.mother_surface_origin_point_x_zero, Vector3( + self.division_system.distances[count]+self.inner_frame_type.b*0.5, 0, 0)) + point2 = Point.translate(self.mother_surface_origin_point_x_zero, Vector3( + self.division_system.distances[count+1]-self.inner_frame_type.b*0.5, 0, 0)) + point3 = Point.translate(self.mother_surface_origin_point_x_zero, Vector3( + self.division_system.distances[count+1]-self.inner_frame_type.b*0.5, self.inner_height, 0)) + point4 = Point.translate(self.mother_surface_origin_point_x_zero, Vector3( + self.division_system.distances[count]+self.inner_frame_type.b*0.5, self.inner_height, 0)) + self.panel_objects.append( + Panel.by_polycurve_thickness( + PolyCurve.by_points([point1, point2, point3, point4, point1]), 184, 0, "innerpanel", rgb_to_int([255, 240, 160])) + ) + count = count + 1 + except: + # Last panel + point1 = Point.translate(self.mother_surface_origin_point_x_zero, Vector3( + self.division_system.distances[count]+self.inner_frame_type.b*0.5, 0, 0)) + point2 = Point.translate(self.mother_surface_origin_point_x_zero, Vector3( + self.inner_width+self.left_frame_type.b, 0, 0)) + point3 = Point.translate(self.mother_surface_origin_point_x_zero, Vector3( + self.inner_width+self.left_frame_type.b, self.inner_height, 0)) + point4 = Point.translate(self.mother_surface_origin_point_x_zero, Vector3( + self.division_system.distances[count]+self.inner_frame_type.b*0.5, self.inner_height, 0)) + self.panel_objects.append( + Panel.by_polycurve_thickness( + PolyCurve.by_points([point1, point2, point3, point4, point1]), 184, 0, "innerpanel", rgb_to_int([255, 240, 160])) + ) + count = count + 1 + + def __inner_mother_surface(self): + """Determines the inner mother surface dimensions and creates its symbolic representation. + Calculates the inner width and height by subtracting the frame widths from the total width and height. It then constructs a symbolic PolyCurve representing the mother surface within the rectangle system's frames. + + #### Effects: + - Updates `inner_width` and `inner_height` attributes based on frame dimensions. + - Creates a symbolic PolyCurve `symbolic_inner_mother_surface` representing the inner mother surface. + """ + # Inner mother surface is the surface within the outer frames dependent on the width of the outer frametypes. + self.inner_width = self.width-self.left_frame_type.b-self.right_frame_type.b + self.inner_height = self.height-self.top_frame_type.b-self.bottom_frame_type.b + self.mother_surface_origin_point = Point( + self.left_frame_type.b, self.bottom_frame_type.b, 0) + self.mother_surface_origin_point_x_zero = Point( + 0, self.bottom_frame_type.b, 0) + self.symbolic_inner_mother_surface = PolyCurve.by_points( + [self.mother_surface_origin_point, + Point.translate(self.mother_surface_origin_point, + Vector3(self.inner_width, 0, 0)), + Point.translate(self.mother_surface_origin_point, Vector3( + self.inner_width, self.inner_height, 0)), + Point.translate(self.mother_surface_origin_point, + Vector3(0, self.inner_height, 0)), + self.mother_surface_origin_point] + ) + + def __inner_frames(self): + """Creates inner frame objects based on division distances within the rectangle system. + Utilizes the division distances to place vertical frames across the inner width of the rectangle system. These frames are represented both as Frame objects within the system and as symbolic lines for visualization. + + #### Effects: + - Generates Frame objects for each division, placing them vertically within the rectangle system. + - Populates `inner_frame_objects` with these Frame instances. + - Adds symbolic representations of these frames to `symbolic_inner_grids`. + """ + for i in self.division_system.distances: + start_point = Point.translate( + self.mother_surface_origin_point_x_zero, Vector3(i, 0, 0)) + end_point = Point.translate( + self.mother_surface_origin_point_x_zero, Vector3(i, self.inner_height, 0)) + self.inner_frame_objects.append( + Frame.by_start_point_endpoint_curve_justifiction( + start_point, end_point, self.inner_frame_type.curve, "innerframe", "center", "top", 0, self.material) + ) + self.symbolic_inner_grids.append( + Line(start=start_point, end=end_point)) + + def __outer_frames(self): + """Generates the outer frame objects for the rectangle system. + Creates Frame objects for the bottom, top, left, and right boundaries of the rectangle system. Each frame is defined by its start and end points, along with its type and material. Symbolic lines representing these frames are also generated for visualization. + + #### Effects: + - Creates Frame instances for the outer boundaries of the rectangle system and adds them to `outer_frame_objects`. + - Generates symbolic Line instances for each outer frame and adds them to `symbolic_outer_grids`. + """ + bottomframe = Frame.by_start_point_endpoint_curve_justifiction(Point(0, 0, 0), Point( + self.width, 0, 0), self.bottom_frame_type.curve, "bottomframe", "left", "top", 0, self.material) + self.symbolic_outer_grids.append( + Line(start=Point(0, 0, 0), end=Point(self.width, 0, 0))) + + topframe = Frame.by_start_point_endpoint_curve_justifiction(Point(0, self.height, 0), Point( + self.width, self.height, 0), self.top_frame_type.curve, "bottomframe", "right", "top", 0, self.material) + self.symbolic_outer_grids.append( + Line(start=Point(0, self.height, 0), end=Point(self.width, self.height, 0))) + + leftframe = Frame.by_start_point_endpoint_curve_justifiction(Point(0, self.bottom_frame_type.b, 0), Point( + 0, self.height-self.top_frame_type.b, 0), self.left_frame_type.curve, "leftframe", "right", "top", 0, self.material) + self.symbolic_outer_grids.append(Line(start=Point( + 0, self.bottom_frame_type.b, 0), end=Point(0, self.height-self.top_frame_type.b, 0))) + + rightframe = Frame.by_start_point_endpoint_curve_justifiction(Point(self.width, self.bottom_frame_type.b, 0), Point( + self.width, self.height-self.top_frame_type.b, 0), self.right_frame_type.curve, "leftframe", "left", "top", 0, self.material) + self.symbolic_outer_grids.append(Line(start=Point(self.width, self.bottom_frame_type.b, 0), end=Point( + self.width, self.height-self.top_frame_type.b, 0))) + + self.outer_frame_objects.append(bottomframe) + self.outer_frame_objects.append(topframe) + self.outer_frame_objects.append(leftframe) + self.outer_frame_objects.append(rightframe) + + def by_width_height_divisionsystem_studtype(self, width: float, height: float, frame_width: float, frame_height: float, division_system: DivisionSystem, filling: bool) -> 'RectangleSystem': + """Configures the rectangle system with specified dimensions, division system, and frame types. + This method sets the dimensions of the rectangle system, configures the frame types based on the provided dimensions, and applies a division system to generate inner frames. Optionally, it can also fill the system with panels based on the inner divisions. + + #### Parameters: + - `width` (float): The width of the rectangle system. + - `height` (float): The height of the rectangle system. + - `frame_width` (float): The width of the frame elements. + - `frame_height` (float): The height (thickness) of the frame elements. + - `division_system` (DivisionSystem): The division system to apply for inner divisions. + - `filling` (bool): A flag indicating whether to fill the divided areas with panels. + + #### Returns: + `RectangleSystem`: The instance itself, updated with the new configuration. + + #### Example usage: + ```python + rectangle_system = RectangleSystem() + rectangle_system.by_width_height_divisionsystem_studtype(2000, 3000, 38, 184, divisionSystem, True) + ``` + """ + self.width = width + self.height = height + self.bottom_frame_type = Rectangle( + "bottom_frame_type", frame_width, frame_height) + self.top_frame_type = Rectangle( + "top_frame_type", frame_width, frame_height) + self.left_frame_type = Rectangle( + "left_frame_type", frame_width, frame_height) + self.right_frame_type = Rectangle( + "left_frame_type", frame_width, frame_height) + self.inner_frame_type = Rectangle( + "inner_frame_type", frame_width, frame_height) + self.division_system = division_system + self.__inner_mother_surface() + self.__inner_frames() + self.__outer_frames() + if filling: + self.__inner_panels() + else: + pass + return self + + +class pattern_system: + """The `pattern_system` class is designed to define and manipulate patterns for architectural or design applications. It is capable of generating various patterns based on predefined or dynamically generated parameters.""" + def __init__(self): + """Initializes a new pattern_system instance.""" + self.type = __class__.__name__ + self.name = None + self.id = generateID() + self.pattern = None + self.basepanels = [] # contains a list with basepanels of the system + # contains a list sublists with Vector3 which represent the repetition of the system + self.vectors = [] + + def stretcher_bond_with_joint(self, name: str, brick_width: float, brick_length: float, brick_height: float, joint_width: float, joint_height: float): + """Configures a stretcher bond pattern with joints for the pattern_system. + Establishes the fundamental vectors and base panels for a stretcher bond, taking into account brick dimensions and joint sizes. This pattern alternates bricks in each row, offsetting them by half a brick length. + + #### Parameters: + - `name` (str): Name of the pattern configuration. + - `brick_width` (float): Width of the brick. + - `brick_length` (float): Length of the brick. + - `brick_height` (float): Height of the brick. + - `joint_width` (float): Width of the joint between bricks. + - `joint_height` (float): Height of the joint between brick layers. + + #### Returns: + The instance itself, updated with the stretcher bond pattern configuration. + + #### Example usage: + ```python + + ``` + """ + self.name = name + # Vectors of panel 1 + V1 = Vector3(0, (brick_height + joint_height)*2, 0) # dy + V2 = Vector3(brick_length+joint_width, 0, 0) # dx + self.vectors.append([V1, V2]) + + # Vectors of panel 2 + V3 = Vector3(0, (brick_height + joint_height) * 2, 0) # dy + V4 = Vector3(brick_length + joint_width, 0, 0) # dx + self.vectors.append([V3, V4]) + + dx = (brick_length+joint_width)/2 + dy = brick_height+joint_height + + PC1 = PolyCurve().by_points([Point(0, 0, 0), Point(0, brick_height, 0), Point( + brick_length, brick_height, 0), Point(brick_length, 0, 0), Point(0, 0, 0)]) + PC2 = PolyCurve().by_points([Point(dx, dy, 0), Point(dx, brick_height+dy, 0), Point( + brick_length+dx, brick_height+dy, 0), Point(brick_length+dx, dy, 0), Point(dx, dy, 0)]) + BasePanel1 = Panel.by_polycurve_thickness( + PC1, brick_width, 0, "BasePanel1", BaseBrick.colorint) + BasePanel2 = Panel.by_polycurve_thickness( + PC2, brick_width, 0, "BasePanel2", BaseBrick.colorint) + + self.basepanels.append(BasePanel1) + self.basepanels.append(BasePanel2) + return self + + def tile_bond_with_joint(self, name: str, tile_width: float, tile_height: float, tile_thickness: float, joint_width: float, joint_height: float): + """Configures a tile bond pattern with specified dimensions and joint sizes for the pattern_system. + Defines a simple tiling pattern where tiles are laid out in rows and columns, separated by specified joint widths and heights. This method sets up base panels to represent individual tiles and their arrangement vectors. + + #### Parameters: + - `name` (str): The name of the tile bond pattern configuration. + - `tile_width` (float): The width of a single tile. + - `tile_height` (float): The height of a single tile. + - `tile_thickness` (float): The thickness of the tile. + - `joint_width` (float): The width of the joint between adjacent tiles. + - `joint_height` (float): The height of the joint between tile rows. + + #### Returns: + The instance itself, updated with the tile bond pattern configuration. + + #### Example Usage: + ```python + pattern_system = pattern_system() + pattern_system.tile_bond_with_joint('TilePattern', 200, 300, 10, 5, 5) + ``` + This configures the `pattern_system` with a tile bond pattern named 'TilePattern', where each tile measures 200x300x10 units, with 5 units of spacing between tiles. + """ + self.name = name + # Vectors of panel 1 + V1 = Vector3(0, (tile_height + joint_height), 0) # dy + V2 = Vector3(tile_width+joint_width, 0, 0) # dx + self.vectors.append([V1, V2]) + + PC1 = PolyCurve().by_points([Point(0, 0, 0), Point(0, tile_height, 0), Point( + tile_width, tile_height, 0), Point(tile_width, 0, 0)]) + BasePanel1 = Panel.by_polycurve_thickness( + PC1, tile_thickness, 0, "BasePanel1", BaseBrick.colorint) + + self.basepanels.append(BasePanel1) + return self + + def cross_bond_with_joint(self, name: str, brick_width: float, brick_length: float, brick_height: float, joint_width: float, joint_height: float): + """Configures a cross bond pattern with joints for the pattern_system. + Sets up a complex brick laying pattern combining stretcher (lengthwise) and header (widthwise) bricks in alternating rows, creating a cross bond appearance. This method defines the base panels and their positioning vectors to achieve the cross bond pattern. + + #### Parameters: + - `name` (str): The name of the cross bond pattern configuration. + - `brick_width` (float): The width of a single brick. + - `brick_length` (float): The length of the brick. + - `brick_height` (float): The height of the brick layer. + - `joint_width` (float): The width of the joint between bricks. + - `joint_height` (float): The height of the joint between brick layers. + + #### Returns: + The instance itself, updated with the cross bond pattern configuration. + + #### Example Usage: + ```python + pattern_system = pattern_system() + pattern_system.cross_bond_with_joint('CrossBondPattern', 90, 190, 80, 10, 10) + ``` + In this configuration, `pattern_system` is set to a cross bond pattern named 'CrossBondPattern', with bricks measuring 90x190x80 units and 10 units of joint spacing in both directions. + """ + self.name = name + lagenmaat = brick_height + joint_height + # Vectors of panel 1 (strek) + V1 = Vector3(0, (brick_height + joint_height) * 4, 0) # dy spacing + V2 = Vector3(brick_length + joint_width, 0, 0) # dx spacing + self.vectors.append([V1, V2]) + + # Vectors of panel 2 (koppen 1) + V3 = Vector3(0, (brick_height + joint_height) * 2, 0) # dy spacing + V4 = Vector3(brick_length + joint_width, 0, 0) # dx spacing + self.vectors.append([V3, V4]) + + dx2 = (brick_width + joint_width)/2 # start x offset + dy2 = lagenmaat # start y offset + + # Vectors of panel 3 (strekken) + V5 = Vector3(0, (brick_height + joint_height) * 4, 0) # dy spacing + V6 = Vector3(brick_length + joint_width, 0, 0) # dx spacing + self.vectors.append([V5, V6]) + + dx3 = (brick_length + joint_width)/2 # start x offset + dy3 = lagenmaat * 2 # start y offset + + # Vectors of panel 4 (koppen 2) + V7 = Vector3(0, (brick_height + joint_height) * 2, 0) # dy spacing + V8 = Vector3(brick_length + joint_width, 0, 0) # dx spacing + self.vectors.append([V7, V8]) + + dx4 = (brick_width + joint_width)/2 + \ + (brick_width + joint_width) # start x offset + dy4 = lagenmaat # start y offset + + PC1 = PolyCurve().by_points([Point(0, 0, 0), Point(0, brick_height, 0), Point( + brick_length, brick_height, 0), Point(brick_length, 0, 0), Point(0, 0, 0)]) + PC2 = PolyCurve().by_points([Point(dx2, dy2, 0), Point(dx2, brick_height+dy2, 0), Point( + brick_width+dx2, brick_height+dy2, 0), Point(brick_width+dx2, dy2, 0), Point(dx2, dy2, 0)]) + PC3 = PolyCurve().by_points([Point(dx3, dy3, 0), Point(dx3, brick_height+dy3, 0), Point( + brick_length+dx3, brick_height+dy3, 0), Point(brick_length+dx3, dy3, 0), Point(dx3, dy3, 0)]) + PC4 = PolyCurve().by_points([Point(dx4, dy4, 0), Point(dx4, brick_height+dy4, 0), Point( + brick_width+dx4, brick_height+dy4, 0), Point(brick_width+dx4, dy4, 0), Point(dx4, dy4, 0)]) + + BasePanel1 = Panel.by_polycurve_thickness( + PC1, brick_width, 0, "BasePanel1", BaseBrick.colorint) + BasePanel2 = Panel.by_polycurve_thickness( + PC2, brick_width, 0, "BasePanel2", BaseBrick.colorint) + BasePanel3 = Panel.by_polycurve_thickness( + PC3, brick_width, 0, "BasePanel3", BaseBrick.colorint) + BasePanel4 = Panel.by_polycurve_thickness( + PC4, brick_width, 0, "BasePanel4", BaseBrickYellow.colorint) + + self.basepanels.append(BasePanel1) + self.basepanels.append(BasePanel2) + self.basepanels.append(BasePanel3) + self.basepanels.append(BasePanel4) + + return self + + +def pattern_geom(pattern_system, width: float, height: float, start_point: Point = None) -> list[Panel]: + """Generates a geometric pattern based on a pattern_system within a specified area. + Takes a pattern_system and fills a defined width and height area starting from an optional start point with the pattern described by the system. + + #### Parameters: + - `pattern_system`: The pattern_system instance defining the pattern. + - `width` (float): Width of the area to fill with the pattern. + - `height` (float): Height of the area to fill with the pattern. + - `start_point` (Point, optional): Starting point for the pattern generation. + + #### Returns: + `list[Panel]`: A list of Panel instances constituting the generated pattern. + + #### Example usage: + ```python + + ``` + """ + start_point = start_point or Point(0, 0, 0) + test = pattern_system + panels = [] + + for i, j in zip(test.basepanels, test.vectors): + ny = int(height / (j[0].y)) # number of panels in y-direction + nx = int(width / (j[1].x)) # number of panels in x-direction + PC = i.origincurve + thickness = i.thickness + color = i.colorint + + # YX ARRAY + yvectdisplacement = j[0] + yvector = Point.to_vector(start_point) + xvectdisplacement = j[1] + xvector = Vector3(0, 0, 0) + + ylst = [] + for k in range(ny): + yvector = Vector3.sum(yvectdisplacement, yvector) + for l in range(nx): + # Copy in x-direction + xvector = Vector3.sum(xvectdisplacement, xvector) + xyvector = Vector3.sum(yvector, xvector) + # translate curve in x and y-direction + PCNew = PolyCurve.copy_translate(PC, xyvector) + pan = Panel.by_polycurve_thickness( + PCNew, thickness, 0, "name", color) + panels.append(pan) + xvector = Vector3.sum( + xvectdisplacement, Vector3(-test.basepanels[0].origincurve.curves[1].length, 0, 0)) + return panels + + +def fillin(perimeter: PolyCurve2D, pattern: pattern_geom) -> pattern_system: + """Fills in a given perimeter with a specified pattern. + Uses a bounding box to define the perimeter within which a pattern is applied, based on a geometric pattern generation function. + + #### Parameters: + - `perimeter` (PolyCurve2D): The 2D perimeter to fill in. + - `pattern` (function): The pattern generation function to apply within the perimeter. + + #### Returns: + `pattern_system`: A pattern_system object that represents the filled-in area. + + #### Example usage: + ```python + + ``` + """ + + bb = BoundingBox2d().by_points(perimeter.points) + + for pt in bb.corners: + project.objects.append(pt) + bb_perimeter = PolyCurve.by_points(bb.corners) + + return [bb_perimeter] + +class Matrix: + def __init__(self, matrix=None): + if matrix is None: + self.matrix = [[0 for _ in range(4)] for _ in range(4)] + else: + self.matrix = matrix + + def add(self, other): + if self.shape() != other.shape(): + raise ValueError("Matrices must have the same dimensions") + return Matrix([[self.matrix[i][j] + other.matrix[i][j] for j in range(len(self.matrix[0]))] for i in range(len(self.matrix))]) + + def all(self, axis=None): + if axis is None: + return all(all(row) for row in self.matrix) + elif axis == 0: + return [all(self.matrix[row][col] for row in range(len(self.matrix))) for col in range(len(self.matrix[0]))] + elif axis == 1: + return [all(col) for col in self.matrix] + else: + raise ValueError("Axis must be None, 0, or 1") + + def any(self, axis=None): + if axis is None: + return any(any(row) for row in self.matrix) + elif axis == 0: + return [any(self.matrix[row][col] for row in range(len(self.matrix))) for col in range(len(self.matrix[0]))] + elif axis == 1: + return [any(col) for col in self.matrix] + else: + raise ValueError("Axis must be None, 0, or 1") + + def argmax(self, axis=None): + if axis is None: + flat_list = [item for sublist in self.matrix for item in sublist] + return flat_list.index(max(flat_list)) + elif axis == 0: + return [max(range(len(self.matrix)), key=lambda row: self.matrix[row][col]) for col in range(len(self.matrix[0]))] + elif axis == 1: + return [max(range(len(row)), key=lambda col: row[col]) for row in self.matrix] + else: + raise ValueError("Axis must be None, 0, or 1") + + def argmin(self, axis=None): + if axis is None: + flat_list = [item for sublist in self.matrix for item in sublist] + return flat_list.index(min(flat_list)) + elif axis == 0: + return [min(range(len(self.matrix)), key=lambda row: self.matrix[row][col]) for col in range(len(self.matrix[0]))] + elif axis == 1: + return [min(range(len(row)), key=lambda col: row[col]) for row in self.matrix] + else: + raise ValueError("Axis must be None, 0, or 1") + + def argpartition(self, kth, axis=0): + def partition(arr, kth): + pivot = arr[kth] + less = [i for i in range(len(arr)) if arr[i] < pivot] + equal = [i for i in range(len(arr)) if arr[i] == pivot] + greater = [i for i in range(len(arr)) if arr[i] > pivot] + return less + equal + greater + + if axis == 0: + return [partition([self.matrix[row][col] for row in range(len(self.matrix))], kth) for col in range(len(self.matrix[0]))] + elif axis == 1: + return [partition(row, kth) for row in self.matrix] + + def argsort(self, axis=0): + if axis == 0: + return [[row for row, val in sorted(enumerate(col), key=lambda x: x[1])] for col in zip(*self.matrix)] + elif axis == 1: + return [list(range(len(self.matrix[0]))) for _ in self.matrix] + + def astype(self, dtype): + cast_matrix = [[dtype(item) for item in row] for row in self.matrix] + return Matrix(cast_matrix) + + def byteswap(self, inplace=False): + if inplace: + for i in range(len(self.matrix)): + for j in range(len(self.matrix[i])): + self.matrix[i][j] = ~self.matrix[i][j] + return self + else: + new_matrix = [[~item for item in row] for row in self.matrix] + return Matrix(new_matrix) + + def choose(self, choices, mode='raise'): + if mode != 'raise': + raise NotImplementedError("Only 'raise' mode is implemented") + + chosen = [[choices[item] for item in row] for row in self.matrix] + return Matrix(chosen) + + def compress(self, condition, axis=None): + if axis == 0: + compressed = [row for row, cond in zip( + self.matrix, condition) if cond] + return Matrix(compressed) + else: + raise NotImplementedError("Axis other than 0 is not implemented") + + def clip(self, min=None, max=None): + clipped_matrix = [] + for row in self.matrix: + clipped_row = [max if max is not None and val > + max else min if min is not None and val < min else val for val in row] + clipped_matrix.append(clipped_row) + return Matrix(clipped_matrix) + + def conj(self): + conjugated_matrix = [[complex(item).conjugate() + for item in row] for row in self.matrix] + return Matrix(conjugated_matrix) + + def conjugate(self): + return self.conj() + + def copy(self): + copied_matrix = copy.deepcopy(self.matrix) + return Matrix(copied_matrix) + + def cumprod(self, axis=None): + if axis is None: + flat_list = self.flatten() + cumprod_list = [] + cumprod = 1 + for item in flat_list: + cumprod *= item + cumprod_list.append(cumprod) + return Matrix([cumprod_list]) + else: + raise NotImplementedError( + "Axis handling not implemented in this example") + + def cumsum(self, axis=None): + if axis is None: + flat_list = self.flatten() + cumsum_list = [] + cumsum = 0 + for item in flat_list: + cumsum += item + cumsum_list.append(cumsum) + return Matrix([cumsum_list]) + else: + raise NotImplementedError( + "Axis handling not implemented in this example") + + def diagonal(self, offset=0): + return [self.matrix[i][i + offset] for i in range(len(self.matrix)) if 0 <= i + offset < len(self.matrix[i])] + + def dump(self, file): + with open(file, 'wb') as f: + pickle.dump(self.matrix, f) + + def dumps(self): + return pickle.dumps(self.matrix) + + def fill(self, value): + for i in range(len(self.matrix)): + for j in range(len(self.matrix[i])): + self.matrix[i][j] = value + + @staticmethod + def from_points(from_point: Point, to_point: Point): + Vz = Vector3.by_two_points(from_point, to_point) + Vz = Vector3.normalize(Vz) + Vzglob = Vector3(0, 0, 1) + Vx = Vector3.cross_product(Vz, Vzglob) + if Vector3.length(Vx) == 0: + Vx = Vector3(1, 0, 0) if Vz.x != 1 else Vector3(0, 1, 0) + Vx = Vector3.normalize(Vx) + Vy = Vector3.cross_product(Vx, Vz) + + return Matrix([ + [Vx.x, Vy.x, Vz.x, from_point.x], + [Vx.y, Vy.y, Vz.y, from_point.y], + [Vx.z, Vy.z, Vz.z, from_point.z], + [0, 0, 0, 1] + ]) + + def flatten(self): + return [item for sublist in self.matrix for item in sublist] + + def getA(self): + return self.matrix + + def getA1(self): + return [item for sublist in self.matrix for item in sublist] + + def getH(self): + conjugate_transposed = [[complex(self.matrix[j][i]).conjugate() for j in range( + len(self.matrix))] for i in range(len(self.matrix[0]))] + return Matrix(conjugate_transposed) + + def getI(self): + raise NotImplementedError( + "Matrix inversion is a complex operation not covered in this simple implementation.") + + def getT(self): + return self.transpose() + + def getfield(self, dtype, offset=0): + raise NotImplementedError( + "This method is conceptual and depends on structured data support within the Matrix.") + + def item(self, *args): + if len(args) == 1: + index = args[0] + rows, cols = len(self.matrix), len(self.matrix[0]) + return self.matrix[index // cols][index % cols] + elif len(args) == 2: + return self.matrix[args[0]][args[1]] + else: + raise ValueError("Invalid number of indices.") + + def itemset(self, *args): + if len(args) == 2: + index, value = args + rows, cols = len(self.matrix), len(self.matrix[0]) + self.matrix[index // cols][index % cols] = value + elif len(args) == 3: + row, col, value = args + self.matrix[row][col] = value + else: + raise ValueError("Invalid number of arguments.") + + def max(self, axis=None): + if axis is None: + return max(item for sublist in self.matrix for item in sublist) + elif axis == 0: + return [max(self.matrix[row][col] for row in range(len(self.matrix))) for col in range(len(self.matrix[0]))] + elif axis == 1: + return [max(row) for row in self.matrix] + else: + raise ValueError("Invalid axis.") + + def mean(self, axis=None): + if axis is None: + flat_list = self.flatten() + return sum(flat_list) / len(flat_list) + elif axis == 0: + return [sum(self.matrix[row][col] for row in range(len(self.matrix))) / len(self.matrix) for col in range(len(self.matrix[0]))] + elif axis == 1: + return [sum(row) / len(row) for row in self.matrix] + else: + raise ValueError("Axis must be None, 0, or 1") + + def min(self, axis=None): + if axis is None: + return min(item for sublist in self.matrix for item in sublist) + elif axis == 0: + return [min(self.matrix[row][col] for row in range(len(self.matrix))) for col in range(len(self.matrix[0]))] + elif axis == 1: + return [min(row) for row in self.matrix] + else: + raise ValueError("Invalid axis.") + + @staticmethod + def zeros(rows, cols): + return Matrix([[0 for _ in range(cols)] for _ in range(rows)]) + + @staticmethod + def participation(self): + pass + + def prod(self, axis=None): + if axis is None: + return reduce(lambda x, y: x * y, [item for sublist in self.matrix for item in sublist], 1) + elif axis == 0: + return [reduce(lambda x, y: x * y, [self.matrix[row][col] for row in range(len(self.matrix))], 1) for col in range(len(self.matrix[0]))] + elif axis == 1: + return [reduce(lambda x, y: x * y, row, 1) for row in self.matrix] + else: + raise ValueError("Invalid axis.") + + def ptp(self, axis=None): + if axis is None: + flat_list = [item for sublist in self.matrix for item in sublist] + return max(flat_list) - min(flat_list) + elif axis == 0: + return [max([self.matrix[row][col] for row in range(len(self.matrix))]) - min([self.matrix[row][col] for row in range(len(self.matrix))]) for col in range(len(self.matrix[0]))] + elif axis == 1: + return [max(row) - min(row) for row in self.matrix] + else: + raise ValueError("Invalid axis.") + + def put(self, indices, values): + if len(indices) != len(values): + raise ValueError("Length of indices and values must match.") + flat_list = self.ravel() + for index, value in zip(indices, values): + flat_list[index] = value + + @staticmethod + def random(rows, cols): + import random + return Matrix([[random.random() for _ in range(cols)] for _ in range(rows)]) + + def ravel(self): + return [item for sublist in self.matrix for item in sublist] + + def repeat(self, repeats, axis=None): + if axis is None: + flat_list = self.ravel() + repeated = [item for item in flat_list for _ in range(repeats)] + return Matrix([repeated]) + elif axis == 0: + repeated_matrix = [ + row for row in self.matrix for _ in range(repeats)] + elif axis == 1: + repeated_matrix = [ + [item for item in row for _ in range(repeats)] for row in self.matrix] + else: + raise ValueError("Invalid axis.") + return Matrix(repeated_matrix) + + def reshape(self, rows, cols): + flat_list = self.flatten() + if len(flat_list) != rows * cols: + raise ValueError( + "The total size of the new array must be unchanged.") + reshaped = [flat_list[i * cols:(i + 1) * cols] for i in range(rows)] + return Matrix(reshaped) + + def resize(self, new_shape): + new_rows, new_cols = new_shape + current_rows, current_cols = len(self.matrix), len( + self.matrix[0]) if self.matrix else 0 + if new_rows < current_rows: + self.matrix = self.matrix[:new_rows] + else: + for _ in range(new_rows - current_rows): + self.matrix.append([0] * current_cols) + for row in self.matrix: + if new_cols < current_cols: + row[:] = row[:new_cols] + else: + row.extend([0] * (new_cols - current_cols)) + + def round(self, decimals=0): + rounded_matrix = [[round(item, decimals) + for item in row] for row in self.matrix] + return Matrix(rounded_matrix) + + def searchsorted(self, v, side='left'): + flat_list = self.flatten() + i = 0 + if side == 'left': + while i < len(flat_list) and flat_list[i] < v: + i += 1 + elif side == 'right': + while i < len(flat_list) and flat_list[i] <= v: + i += 1 + else: + raise ValueError("side must be 'left' or 'right'") + return i + + def setfield(self, val, dtype, offset=0): + raise NotImplementedError( + "Structured data operations are not supported in this Matrix class.") + + def setflags(self, write=None, align=None, uic=None): + print("This Matrix class does not support setting flags directly.") + + def shape(self): + return len(self.matrix), len(self.matrix[0]) + + def sort(self, axis=-1): + if axis == -1 or axis == 1: + for row in self.matrix: + row.sort() + elif axis == 0: + transposed = [[self.matrix[j][i] for j in range( + len(self.matrix))] for i in range(len(self.matrix[0]))] + for row in transposed: + row.sort() + self.matrix = [[transposed[j][i] for j in range( + len(transposed))] for i in range(len(transposed[0]))] + else: + raise ValueError("Axis out of range.") + + def squeeze(self): + squeezed_matrix = [row for row in self.matrix if any(row)] + return Matrix(squeezed_matrix) + + def std(self, axis=None, ddof=0): + var = self.var(axis=axis, ddof=ddof) + if isinstance(var, list): + return [x ** 0.5 for x in var] + else: + return var ** 0.5 + + def subtract(self, other): + if self.shape() != other.shape(): + raise ValueError("Matrices must have the same dimensions") + return Matrix([[self.matrix[i][j] - other.matrix[i][j] for j in range(len(self.matrix[0]))] for i in range(len(self.matrix))]) + + def sum(self, axis=None): + if axis is None: + return sum(sum(row) for row in self.matrix) + elif axis == 0: + return [sum(self.matrix[row][col] for row in range(len(self.matrix))) for col in range(len(self.matrix[0]))] + elif axis == 1: + return [sum(row) for row in self.matrix] + else: + raise ValueError("Axis must be None, 0, or 1") + + def swapaxes(self, axis1, axis2): + if axis1 == 0 and axis2 == 1 or axis1 == 1 and axis2 == 0: + return Matrix([[self.matrix[j][i] for j in range(len(self.matrix))] for i in range(len(self.matrix[0]))]) + else: + raise ValueError("Axis values out of range for a 2D matrix.") + + def take(self, indices, axis=None): + if axis is None: + flat_list = [item for sublist in self.matrix for item in sublist] + return Matrix([flat_list[i] for i in indices]) + elif axis == 0: + return Matrix([self.matrix[i] for i in indices]) + else: + raise ValueError( + "Axis not supported or out of range for a 2D matrix.") + + def tobytes(self): + byte_array = bytearray() + for row in self.matrix: + for item in row: + byte_array.extend(struct.pack('i', item)) + return bytes(byte_array) + + def tofile(self, fid, sep="", format="%s"): + if isinstance(fid, str): + with open(fid, 'wb' if sep == "" else 'w') as f: + self._write_to_file(f, sep, format) + else: + self._write_to_file(fid, sep, format) + + def _write_to_file(self, file, sep, format): + if sep == "": + file.write(self.tobytes()) + else: + for row in self.matrix: + line = sep.join(format % item for item in row) + "\n" + file.write(line) + + def tostring(self): + for row in self.matrix: + print(' '.join(map(str, row))) + + def trace(self, offset=0): + rows, cols = len(self.matrix), len(self.matrix[0]) + return sum(self.matrix[i][i + offset] for i in range(min(rows, cols - offset)) if 0 <= i + offset < cols) + + def transpose(self): + transposed = [[self.matrix[j][i] for j in range( + len(self.matrix))] for i in range(len(self.matrix[0]))] + return Matrix(transposed) + + def var(self, axis=None, ddof=0): + if axis is None: + flat_list = self.flatten() + mean = sum(flat_list) / len(flat_list) + return sum((x - mean) ** 2 for x in flat_list) / (len(flat_list) - ddof) + elif axis == 0 or axis == 1: + means = self.mean(axis=axis) + if axis == 0: + return [sum((self.matrix[row][col] - means[col]) ** 2 for row in range(len(self.matrix))) / (len(self.matrix) - ddof) for col in range(len(self.matrix[0]))] + else: + return [sum((row[col] - means[idx]) ** 2 for col in range(len(row))) / (len(row) - ddof) for idx, row in enumerate(self.matrix)] + else: + raise ValueError("Axis must be None, 0, or 1") + + def _validate(self): + rows = len(self.matrix) + cols = len(self.matrix[0]) if rows > 0 else 0 + return rows, cols + +class CoordinateSystem: + # UNITY VECTORS REQUIRED #TOdo organize resic + """The `CoordinateSystem` class represents a coordinate system in 3D space, defined by an origin point and three orthogonal unit vectors along the X, Y, and Z axes.""" + def __init__(self, origin: Point, x_axis, y_axis, z_axis): + """Initializes a new CoordinateSystem instance. + + #### Parameters: + - `origin` (Point): The origin point of the coordinate system. + - `x_axis` (Vector3): The X-axis direction vector. + - `y_axis` (Vector3): The Y-axis direction vector. + - `z_axis` (Vector3): The Z-axis direction vector. + + #### Example usage: + ```python + origin = Point(0, 0, 0) + x_axis = Vector3(1, 0, 0) + y_axis = Vector3(0, 1, 0) + z_axis = Vector3(0, 0, 1) + coordinate_system = CoordinateSystem(origin, x_axis, y_axis, z_axis) + ``` + """ + + self.id = generateID() + self.type = __class__.__name__ + self.Origin = origin + self.Xaxis = Vector3.normalize(x_axis) + self.Y_axis = Vector3.normalize(y_axis) + self.Z_axis = Vector3.normalize(z_axis) + + def serialize(self) -> dict: + """Serializes the coordinate system's attributes into a dictionary. + + #### Returns: + `dict`: A dictionary containing the serialized attributes of the coordinate system. + + #### Example usage: + ```python + coordinate_system = CoordinateSystem(...) + serialized_cs = coordinate_system.serialize() + ``` + """ + id_value = str(self.id) if not isinstance( + self.id, (str, int, float)) else self.id + return { + 'id': id_value, + 'type': self.type, + 'Origin': self.Origin.serialize(), + 'Xaxis': self.Xaxis.serialize(), + 'Y_axis': self.Y_axis.serialize(), + 'Z_axis': self.Z_axis.serialize() + } + + @staticmethod + def deserialize(data: dict): + """Recreates a CoordinateSystem object from serialized data. + + #### Parameters: + - `data` (dict): The dictionary containing the serialized data of a CoordinateSystem object. + + #### Returns: + `CoordinateSystem`: A new CoordinateSystem object initialized with the data from the dictionary. + + #### Example usage: + ```python + data = {...} + coordinate_system = CoordinateSystem.deserialize(data) + ``` + """ + origin = Point.deserialize(data['Origin']) + x_axis = Vector3.deserialize(data['Xaxis']) + y_axis = Vector3.deserialize(data['Y_axis']) + z_axis = Vector3.deserialize(data['Z_axis']) + return CoordinateSystem(origin, x_axis, y_axis, z_axis) + + # @classmethod + # def by_origin(self, origin: Point): + # self.Origin = origin + # self.Xaxis = X_axis + # self.Y_axis = Y_Axis + # self.Z_axis = Z_Axis + # return self + + @classmethod + def by_origin(self, origin: Point) -> 'CoordinateSystem': + """Creates a CoordinateSystem with a new origin while keeping the standard orientation. + + #### Parameters: + - `origin` (Point): The new origin point of the coordinate system. + + #### Returns: + `CoordinateSystem`: A CoordinateSystem instance with the specified origin and standard axes orientation. + + #### Example usage: + ```python + new_origin = Point(10, 10, 10) + coordinate_system = CoordinateSystem.by_origin(new_origin) + ``` + """ + from abstract.coordinatesystem import X_axis, Y_Axis, Z_Axis + return self(origin, x_axis=X_axis, y_axis=Y_Axis, z_axis=Z_Axis) + + # @staticmethod + # def translate(cs_old, direction): + # CSNew = CoordinateSystem(cs_old.Origin, cs_old.Xaxis, cs_old.Y_axis, cs_old.Z_axis) + # new_origin = Point.translate(CSNew.Origin, direction) + # CSNew.Origin = new_origin + # return CSNew + + @staticmethod + def translate(cs_old, direction: Vector3): + """Translates an existing CoordinateSystem by a given direction vector. + + #### Parameters: + - `cs_old` (CoordinateSystem): The original CoordinateSystem to be translated. + - `direction` (Vector3): The direction vector by which to translate the coordinate system. + + #### Returns: + `CoordinateSystem`: A new CoordinateSystem instance translated according to the direction vector. + + #### Example usage: + ```python + original_cs = CoordinateSystem(...) + direction_vector = Vector3(5, 5, 0) + translated_cs = CoordinateSystem.translate(original_cs, direction_vector) + ``` + """ + from abstract.vector import Vector3 + pt = cs_old.Origin + new_origin = Point.translate(pt, direction) + + X_axis = Vector3(1, 0, 0) + + Y_Axis = Vector3(0, 1, 0) + + Z_Axis = Vector3(0, 0, 1) + + CSNew = CoordinateSystem( + new_origin, x_axis=X_axis, y_axis=Y_Axis, z_axis=Z_Axis) + + CSNew.Origin = new_origin + return CSNew + + @staticmethod + def transform(CS1, CS2): + """Transforms one coordinate system (CS1) to align with another (CS2). + + This method calculates the transformation required to align CS1's axes with those of CS2, including translation and rotation. + + #### Parameters: + - `CS1` (CoordinateSystem): The original coordinate system to be transformed. + - `CS2` (CoordinateSystem): The target coordinate system to align with. + + #### Returns: + `CoordinateSystem`: A new CoordinateSystem instance that has been transformed to align with CS2. + + #### Example usage: + ```python + CS1 = CoordinateSystem(...) + CS2 = CoordinateSystem(...) + transformed_CS = CoordinateSystem.transform(CS1, CS2) + ``` + """ + from abstract.vector import Vector3 + + translation_vector = Vector3.subtract(CS2.Origin, CS1.Origin) + + rotation_matrix = CoordinateSystem.calculate_rotation_matrix( + CS1.Xaxis, CS1.Y_axis, CS1.Z_axis, CS2.Xaxis, CS2.Y_axis, CS2.Z_axis) + + xaxis_transformed = Vector3.dot_product(rotation_matrix, CS1.Xaxis) + yaxis_transformed = Vector3.dot_product(rotation_matrix, CS1.Y_axis) + zaxis_transformed = Vector3.dot_product(rotation_matrix, CS1.Z_axis) + + xaxis_normalized = Vector3.normalize( + Vector3.from_matrix(xaxis_transformed)) + yaxis_normalized = Vector3.normalize( + Vector3.from_matrix(yaxis_transformed)) + zaxis_normalized = Vector3.normalize( + Vector3.from_matrix(zaxis_transformed)) + + new_origin = Point.translate(CS1.Origin, translation_vector) + new_CS = CoordinateSystem( + new_origin, xaxis_normalized, yaxis_normalized, zaxis_normalized) + + return new_CS + + @staticmethod + def translate_origin(origin1: Point, origin2: Point): + """Translates the origin of a coordinate system from one point to another. + + #### Parameters: + - `origin1` (Point): The original origin point. + - `origin2` (Point): The new origin point to translate to. + + #### Returns: + `Point`: The new origin point after translation. + + #### Example usage: + ```python + origin1 = Point(0, 0, 0) + origin2 = Point(5, 5, 5) + new_origin = CoordinateSystem.translate_origin(origin1, origin2) + ``` + """ + origin1_n = Point.to_matrix(origin1) + origin2_n = Point.to_matrix(origin2) + + new_origin_n = origin1_n + (origin2_n - origin1_n) + return Point(new_origin_n[0], new_origin_n[1], new_origin_n[2]) + + @staticmethod + def calculate_rotation_matrix(xaxis1: Vector3, yaxis1: Vector3, zaxis1: Vector3, xaxis2: Vector3, yaxis2: Vector3, zaxis2: Vector3): + """Calculates the rotation matrix needed to align one set of axes with another. + + #### Parameters: + - `xaxis1`, `yaxis1`, `zaxis1`: The original axes vectors. + - `xaxis2`, `yaxis2`, `zaxis2`: The target axes vectors to align with. + + #### Returns: + A matrix representing the rotation required to align the first set of axes with the second. + + #### Example usage: + ```python + xaxis1 = Vector3(1, 0, 0) + yaxis1 = Vector3(0, 1, 0) + zaxis1 = Vector3(0, 0, 1) + xaxis2 = Vector3(0, 1, 0) + yaxis2 = Vector3(-1, 0, 0) + zaxis2 = Vector3(0, 0, 1) + rotation_matrix = CoordinateSystem.calculate_rotation_matrix(xaxis1, yaxis1, zaxis1, xaxis2, yaxis2, zaxis2) + ``` + """ + from abstract.vector import Vector3 + + def transpose(matrix): + return [[matrix[j][i] for j in range(len(matrix))] for i in range(len(matrix[0]))] + + def matrix_multiply(matrix1, matrix2): + result = [] + for i in range(len(matrix1)): + row = [] + for j in range(len(matrix2[0])): + sum = 0 + for k in range(len(matrix2)): + sum += matrix1[i][k] * matrix2[k][j] + row.append(sum) + result.append(row) + return result + + def matrix_inverse(matrix): + determinant = matrix[0][0] * (matrix[1][1] * matrix[2][2] - matrix[1][2] * matrix[2][1]) - \ + matrix[0][1] * (matrix[1][0] * matrix[2][2] - matrix[1][2] * matrix[2][0]) + \ + matrix[0][2] * (matrix[1][0] * matrix[2][1] - + matrix[1][1] * matrix[2][0]) + if determinant == 0: + raise ValueError("Matrix is not invertible") + inv_det = 1.0 / determinant + result = [[0] * 3 for _ in range(3)] + result[0][0] = (matrix[1][1] * matrix[2][2] - + matrix[1][2] * matrix[2][1]) * inv_det + result[0][1] = (matrix[0][2] * matrix[2][1] - + matrix[0][1] * matrix[2][2]) * inv_det + result[0][2] = (matrix[0][1] * matrix[1][2] - + matrix[0][2] * matrix[1][1]) * inv_det + result[1][0] = (matrix[1][2] * matrix[2][0] - + matrix[1][0] * matrix[2][2]) * inv_det + result[1][1] = (matrix[0][0] * matrix[2][2] - + matrix[0][2] * matrix[2][0]) * inv_det + result[1][2] = (matrix[0][2] * matrix[1][0] - + matrix[0][0] * matrix[1][2]) * inv_det + result[2][0] = (matrix[1][0] * matrix[2][1] - + matrix[1][1] * matrix[2][0]) * inv_det + result[2][1] = (matrix[0][1] * matrix[2][0] - + matrix[0][0] * matrix[2][1]) * inv_det + result[2][2] = (matrix[0][0] * matrix[1][1] - + matrix[0][1] * matrix[1][0]) * inv_det + return result + + R1_transpose = transpose([Vector3.to_matrix( + xaxis1), Vector3.to_matrix(yaxis1), Vector3.to_matrix(zaxis1)]) + R2_transpose = transpose([Vector3.to_matrix( + xaxis2), Vector3.to_matrix(yaxis2), Vector3.to_matrix(zaxis2)]) + + return matrix_multiply(R2_transpose, matrix_inverse(R1_transpose)) + + @staticmethod + def normalize(self): + """Normalizes the axes vectors of a given CoordinateSystem to unit vectors. + + This method ensures that the X, Y, and Z axes of the coordinate system are unit vectors (vectors of length 1), which is essential for many calculations involving directions and orientations in 3D space. + + #### Parameters: + - `self` (CoordinateSystem): The coordinate system whose axes vectors are to be normalized. + + #### Returns: + `CoordinateSystem`: A new CoordinateSystem instance with normalized axes vectors. + + #### Example usage: + ```python + CS_before_normalization = CoordinateSystem( + origin=Point(0, 0, 0), + x_axis=Vector3(10, 0, 0), + y_axis=Vector3(0, 10, 0), + z_axis=Vector3(0, 0, 10) + ) + CS_after_normalization = CoordinateSystem.normalize(CS_before_normalization) + # Now, CS_after_normalization's X, Y, and Z axes are unit vectors. + ``` + """ + self.Xaxis = Vector3.normalize(self.Xaxis) + self.Y_axis = Vector3.normalize(self.Y_axis) + self.Z_axis = Vector3.normalize(self.Z_axis) + + @staticmethod + def move_local(cs_old, x: float, y: float, z: float): + """Moves a CoordinateSystem locally along its own axes. + + #### Parameters: + - `cs_old` (CoordinateSystem): The coordinate system to move. + - `x` (float): The distance to move along the local X-axis. + - `y` (float): The distance to move along the local Y-axis. + - `z` (float): The distance to move along the local Z-axis. + + #### Returns: + `CoordinateSystem`: A new CoordinateSystem instance that has been moved according to the specified distances. + + #### Example usage: + ```python + cs_old = CoordinateSystem(...) + moved_cs = CoordinateSystem.move_local(cs_old, 10, 0, 5) + # The returned CoordinateSystem is moved 10 units along its X-axis and 5 units along its Z-axis. + ``` + """ + from abstract.vector import Vector3 + # move coordinatesystem by y in local coordinates(not global) + xloc_vect_norm = cs_old.Xaxis + xdisp = Vector3.scale(xloc_vect_norm, x) + yloc_vect_norm = cs_old.Xaxis + ydisp = Vector3.scale(yloc_vect_norm, y) + zloc_vect_norm = cs_old.Xaxis + zdisp = Vector3.scale(zloc_vect_norm, z) + disp = Vector3.sum3(xdisp, ydisp, zdisp) + CS = CoordinateSystem.translate(cs_old, disp) + return CS + + @staticmethod + def by_point_main_vector(NewOriginCoordinateSystem: Point, DirectionVectorZ: Vector3) -> 'CoordinateSystem': + """Creates a CoordinateSystem with a specified origin and a main direction vector for the Z-axis. + + #### Parameters: + - `NewOriginCoordinateSystem` (Point): The origin point of the new CoordinateSystem. + - `DirectionVectorZ` (Vector3): The main direction vector to define the new Z-axis. + + #### Returns: + `CoordinateSystem`: A CoordinateSystem instance with the specified origin and Z-axis orientation. + + #### Example usage: + ```python + new_origin = Point(0, 0, 0) + main_direction = Vector3(0, 0, 1) + coordinate_system = CoordinateSystem.by_point_main_vector(new_origin, main_direction) + ``` + """ + vz = DirectionVectorZ # LineVector and new Z-axis + vz = Vector3.normalize(vz) # NewZAxis + vx = Vector3.perpendicular(vz)[0] # NewXAxis + try: + vx = Vector3.normalize(vx) # NewXAxisnormalized + except: + # In case of vertical element the length is zero + vx = Vector3(1, 0, 0) + vy = Vector3.perpendicular(vz)[1] # NewYAxis + try: + vy = Vector3.normalize(vy) # NewYAxisnormalized + except: + # In case of vertical element the length is zero + vy = Vector3(0, 1, 0) + CSNew = CoordinateSystem(NewOriginCoordinateSystem, vx, vy, vz) + return CSNew + + def __str__(self): + return f"{__class__.__name__}(Origin = " + f"{self.Origin}, X_axis = {self.Xaxis}, Y_Axis = {self.Y_axis}, Z_Axis = {self.Z_axis})" + + +X_axis = Vector3(1, 0, 0) +Vector3(0, 1, 0) +Vector3(0, 0, 1) +CSGlobal = CoordinateSystem(Point(0, 0, 0), X_axis, Y_Axis, Z_Axis) + + +class Intersect: + def __init__(self): + pass + + def get_line_intersect(line1: Line, line2: Line) -> Point: + p1, p2 = line1.start, line1.end + p1X, p1Y, P1Z = p1.x, p1.y, p1.z + p2X, p2Y, P2Z = p2.x, p2.y, p2.z + + p3, p4 = line2.start, line2.end + p3X, p3Y, P3Z = p3.x, p3.y, p3.z + p4X, p4Y, P4Z = p4.x, p4.y, p4.z + + print(p1X, p1Y, P1Z) + + +class Color: + """Documentation: output returns [r, g, b]""" + + def __init__(self, colorInput=None): + self.colorInput = colorInput + + red = [255, 0, 0] + green = [0, 255, 0] + blue = [0, 0, 255] + + def Components(self, colorInput=None): + """1""" + if colorInput is None: + return f"Error: Example usage Color().{sys._getframe(0).f_code.co_name}('green')" + else: + try: + import json + JSONfile = "library/color/colorComponents.json" + with open(JSONfile, 'r') as file: + components_dict = json.load(file) + checkExist = components_dict.get(str(colorInput)) + if checkExist is not None: + r, g, b, a = components_dict[colorInput] + return [r, g, b] + else: + return f"Invalid {sys._getframe(0).f_code.co_name}-color, check '{JSONfile}' for available {sys._getframe(0).f_code.co_name}-colors." + except: + return f"Error: Color {sys._getframe(0).f_code.co_name} attribute usage is incorrect. Documentation: Color().{sys._getframe(0).f_code.co_name}.__doc__" + + def Hex(self, colorInput=None): + """NAN""" + if colorInput is None: + return f"Error: Example usage Color().{sys._getframe(0).f_code.co_name}('#2ba4ff')" + else: + # validate if value is correct/found + try: + colorInput = colorInput.split("#")[1] + rgb_color = list(int(colorInput[i:i+2], 16) for i in (1, 3, 5)) + return [rgb_color[0], rgb_color[1], rgb_color[2], 255] + + # colorInput = colorInput.lstrip('#') + # return list(int(colorInput[i:i+2], 16) for i in (0, 2, 4)) + except: + return f"Error: Color {sys._getframe(0).f_code.co_name} attribute usage is incorrect. Documentation: Color().{sys._getframe(0).f_code.co_name}.__doc__" + + def rgba_to_hex(self, colorInput=None): + """NAN""" + if colorInput is None: + return f"Error: Example usage Color().{sys._getframe(0).f_code.co_name}('#2ba4ff')" + else: + # validate if value is correct/found + try: + red_i = int(colorInput[0] * 255) + green_i = int(colorInput[1] * 255) + blue_i = int(colorInput[2] * 255) + alpha_i = int(colorInput[3] * 255) + + red_hex = hex(red_i)[2:].zfill(2) + green_hex = hex(green_i)[2:].zfill(2) + blue_hex = hex(blue_i)[2:].zfill(2) + alpha_hex = hex(alpha_i)[2:].zfill(2) + + colorInput = "#" + red_hex + green_hex + blue_hex + alpha_hex + + return colorInput.upper() + + except: + return f"Error: Color {sys._getframe(0).f_code.co_name} attribute usage is incorrect. Documentation: Color().{sys._getframe(0).f_code.co_name}.__doc__" + + def hex_to_rgba(self, colorInput=None): + """NAN""" + if colorInput is None: + return f"Error: Example usage Color().{sys._getframe(0).f_code.co_name}('#2ba4ff')" + else: + # validate if value is correct/found + try: + colorInput = colorInput.lstrip('#') + red_int = int(colorInput[0:2], 16) + green_int = int(colorInput[2:4], 16) + blue_int = int(colorInput[4:6], 16) + + if len(colorInput) == 8: + alpha_int = int(colorInput[6:8], 16) + alpha = round(alpha_int / 255, 2) + else: + alpha = 1.0 + + red = round(red_int / 255, 2) + green = round(green_int / 255, 2) + blue = round(blue_int / 255, 2) + + return [red, green, blue, alpha] + + except: + return f"Error: Color {sys._getframe(0).f_code.co_name} attribute usage is incorrect. Documentation: Color().{sys._getframe(0).f_code.co_name}.__doc__" + + def CMYK(self, colorInput=None): + """NAN""" + if colorInput is None: + return f"Error: Example usage Color().CMYK([0.5, 0.25, 0, 0.2])" + else: + try: + c, m, y, k = colorInput + r = int((1-c) * (1-k) * 255) + g = int((1-m) * (1-k) * 255) + b = int((1-y) * (1-k) * 255) + return [r, g, b] + except: + # add check help attribute + return f"Error: Color {sys._getframe(0).f_code.co_name} attribute usage is incorrect. Documentation: Color().{sys._getframe(0).f_code.co_name}.__doc__" + + def Alpha(self, colorInput=None): + """NAN""" + if colorInput is None: + return f"Error: Example usage Color().{sys._getframe(0).f_code.co_name}([255, 0, 0, 128])" + else: + try: + r, g, b, a = colorInput + return [r, g, b] + except: + return f"Error: Color {sys._getframe(0).f_code.co_name} attribute usage is incorrect. Documentation: Color().{sys._getframe(0).f_code.co_name}.__doc__" + + def Brightness(self, colorInput=None): + """Expected value is int(0) - int(1)""" + if colorInput is None: + return f"Error: Example usage Color().{sys._getframe(0).f_code.co_name}([255, 0, 0, 128])" + else: + try: + if colorInput >= 0 and colorInput <= 1: + r = g = b = int(255 * colorInput) + return [r, g, b] + else: + return f"Error: Color {sys._getframe(0).f_code.co_name} attribute usage is incorrect. Documentation: Color().{sys._getframe(0).f_code.co_name}.__doc__" + except: + return f"Error: Color {sys._getframe(0).f_code.co_name} attribute usage is incorrect. Documentation: Color().{sys._getframe(0).f_code.co_name}.__doc__" + + def RGB(self, colorInput=None): + """NAN""" + if colorInput is None: + return f"Error: Example usage Color().{sys._getframe(0).f_code.co_name}([255, 0, 0])" + else: + try: + r, g, b = colorInput + return [r, g, b] + except: + return f"Error: Color {sys._getframe(0).f_code.co_name} attribute usage is incorrect. Documentation: Color().{sys._getframe(0).f_code.co_name}.__doc__" + + def HSV(self, colorInput=None): + """NAN""" + if colorInput is None: + return f"Error: Example usage Color().{sys._getframe(0).f_code.co_name}()" + else: + try: + h, s, v = colorInput + h /= 60.0 + c = v * s + x = c * (1 - abs(h % 2 - 1)) + m = v - c + if 0 <= h < 1: + r, g, b = c, x, 0 + elif 1 <= h < 2: + r, g, b = x, c, 0 + elif 2 <= h < 3: + r, g, b = 0, c, x + elif 3 <= h < 4: + r, g, b = 0, x, c + elif 4 <= h < 5: + r, g, b = x, 0, c + else: + r, g, b = c, 0, x + return [int((r + m) * 255), int((g + m) * 255), int((b + m) * 255)] + except: + return f"Error: Color {sys._getframe(0).f_code.co_name} attribute usage is incorrect. Documentation: Color().{sys._getframe(0).f_code.co_name}.__doc__" + + def HSL(self, colorInput=None): + """NAN""" + if colorInput is None: + return f"Error: Example usage Color().{sys._getframe(0).f_code.co_name}()" + else: + try: + h, s, l = colorInput + c = (1 - abs(2 * l - 1)) * s + x = c * (1 - abs(h / 60 % 2 - 1)) + m = l - c / 2 + if h < 60: + r, g, b = c, x, 0 + elif h < 120: + r, g, b = x, c, 0 + elif h < 180: + r, g, b = 0, c, x + elif h < 240: + r, g, b = 0, x, c + elif h < 300: + r, g, b = x, 0, c + else: + r, g, b = c, 0, x + r, g, b = int((r + m) * 255), int((g + m) + * 255), int((b + m) * 255) + return [r, g, b] + except: + return f"Error: Color {sys._getframe(0).f_code.co_name} attribute usage is incorrect. Documentation: Color().{sys._getframe(0).f_code.co_name}.__doc__" + + def RAL(self, colorInput=None): + """NAN""" + if colorInput is None: + return f"Error: Example usage Color().{sys._getframe(0).f_code.co_name}(1000)" + else: + try: + # validate if value is correct/found + import json + JSONfile = "library/color/colorRAL.json" + with open(JSONfile, 'r') as file: + ral_dict = json.load(file) + checkExist = ral_dict.get(str(colorInput)) + if checkExist is not None: + r, g, b = ral_dict[str(colorInput)]["rgb"].split("-") + return [int(r), int(g), int(b), 100] + else: + return f"Invalid {sys._getframe(0).f_code.co_name}-color, check '{JSONfile}' for available {sys._getframe(0).f_code.co_name}-colors." + except: + return f"Error: Color {sys._getframe(0).f_code.co_name} attribute usage is incorrect. Documentation: Color().{sys._getframe(0).f_code.co_name}.__doc__" + + def Pantone(self, colorInput=None): + """NAN""" + if colorInput is None: + return f"Error: Example usage Color().{sys._getframe(0).f_code.co_name}()" + else: + try: + import json + JSONfile = "library/color/colorPantone.json" + with open(JSONfile, 'r') as file: + pantone_dict = json.load(file) + checkExist = pantone_dict.get(str(colorInput)) + if checkExist is not None: + PantoneHex = pantone_dict[str(colorInput)]['hex'] + return Color().Hex(PantoneHex) + else: + return f"Invalid {sys._getframe(0).f_code.co_name}-color, check '{JSONfile}' for available {sys._getframe(0).f_code.co_name}-colors." + except: + return f"Error: Color {sys._getframe(0).f_code.co_name} attribute usage is incorrect. Documentation: Color().{sys._getframe(0).f_code.co_name}.__doc__" + + def LRV(self, colorInput=None): + """NAN""" + if colorInput is None: + return f"Error: Example usage Color().{sys._getframe(0).f_code.co_name}()" + else: + try: + b = (colorInput - 0.2126 * 255 - 0.7152 * 255) / 0.0722 + b = int(max(0, min(255, b))) + return [255, 255, b] + except: + return f"Error: Color {sys._getframe(0).f_code.co_name} attribute usage is incorrect. Documentation: Color().{sys._getframe(0).f_code.co_name}.__doc__" + + def rgb_to_int(rgb): + r, g, b = [max(0, min(255, c)) for c in rgb] + return (255 << 24) | (r << 16) | (g << 8) | b + + def __str__(self, colorInput=None): + colorattributes = ["Components", "Hex", "rgba_to_hex", "hex_to_rgba", "CMYK", + "Alpha", "Brightness", "RGB", "HSV", "HSL", "RAL", "Pantone", "LRV"] + if colorInput is None: + header = "Available attributes: \n" + footer = "\nColor().red | Color().green | Color().blue" + return header + '\n'.join([f"Color().{func}()" for func in colorattributes]) + footer + return f"Color().{colorInput}" + + def Info(self, colorInput=None): + pass + + +class Node: + """The `Node` class represents a geometric or structural node within a system, defined by a point in space, along with optional attributes like a direction vector, identifying number, and other characteristics.""" + def __init__(self, point=None, vector=None, number=None, distance=0.0, diameter=None, comments=None): + """"Initializes a new Node instance. + + - `id` (str): A unique identifier for the node. + - `type` (str): The class name, "Node". + - `point` (Point, optional): The location of the node in 3D space. + - `vector` (Vector3, optional): A vector indicating the orientation or direction associated with the node. + - `number` (any, optional): An identifying number or label for the node. + - `distance` (float): A scalar attribute, potentially representing distance from a reference point or another node. + - `diameter` (any, optional): A diameter associated with the node, useful in structural applications. + - `comments` (str, optional): Additional comments or notes about the node. + """ + self.id = generateID() + self.type = __class__.__name__ + self.point = point if isinstance(point, Point) else None + self.vector = vector if isinstance(vector, Vector3) else None + self.number = number + self.distance = distance + self.diameter = diameter + self.comments = comments + + def serialize(self) -> dict: + """Serializes the node's attributes into a dictionary. + + This method allows for the node's properties to be easily stored or transmitted in a dictionary format. + + #### Returns: + `dict`: A dictionary containing the serialized attributes of the node. + """ + id_value = str(self.id) if not isinstance( + self.id, (str, int, float)) else self.id + return { + 'id': id_value, + 'type': self.type, + 'point': self.point.serialize() if self.point else None, + 'vector': self.vector.serialize() if self.vector else None, + 'number': self.number, + 'distance': self.distance, + 'diameter': self.diameter, + 'comments': self.comments + } + + @staticmethod + def deserialize(data: dict) -> 'Node': + """Recreates a Node object from a dictionary of serialized data. + + #### Parameters: + - data (dict): The dictionary containing the node's serialized data. + + #### Returns: + `Node`: A new Node object initialized with the data from the dictionary. + """ + node = Node() + node.id = data.get('id') + node.type = data.get('type') + node.point = Point.deserialize( + data['point']) if data.get('point') else None + node.vector = Vector3.deserialize( + data['vector']) if data.get('vector') else None + node.number = data.get('number') + node.distance = data.get('distance') + node.diameter = data.get('diameter') + node.comments = data.get('comments') + + return node + + # merge + def merge(self): + """Merges this node with others in a project according to defined rules. + + The actual implementation of this method should consider merging nodes based on proximity or other criteria within the project context. + """ + if project.node_merge == True: + pass + else: + pass + + # snap + def snap(self): + """Adjusts the node's position based on snapping criteria. + + This could involve aligning the node to a grid, other nodes, or specific geometric entities. + """ + pass + + def __str__(self) -> str: + """Generates a string representation of the Node. + + #### Returns: + `str`: A string that represents the Node, including its type and potentially other identifying information. + """ + + return f"{self.type}" + + +class Vector3: + """Represents a 3D vector with x, y, and z coordinates.""" + def __init__(self, x: float, y: float, z: float) -> 'Vector3': + """Initializes a new Vector3 instance with the given x, y, and z coordinates. + + - `x` (float): X-coordinate of the vector. + - `y` (float): Y-coordinate of the vector. + - `z` (float): Z-coordinate of the vector. + """ + self.id = generateID() + self.type = __class__.__name__ + self.x: float = 0.0 + self.y: float = 0.0 + self.z: float = 0.0 + + self.x = x + self.y = y + self.z = z + + def serialize(self) -> dict: + """Serializes the Vector3 object into a dictionary. + + #### Returns: + `dict`: A dictionary containing the serialized data of the Vector3 object. + + #### Example usage: + ```python + vector = Vector3(1, 2, 3) + serialized_data = vector.serialize() + # {'id': None, 'type': None, 'x': 1, 'y': 2, 'z': 3} + ``` + """ + id_value = str(self.id) if not isinstance( + self.id, (str, int, float)) else self.id + return { + 'id': id_value, + 'type': self.type, + 'x': self.x, + 'y': self.y, + 'z': self.z + } + + @staticmethod + def deserialize(data): + """Converts a dictionary representation of a vector into a Vector3 object. + This method takes a dictionary containing 'x', 'y', and 'z' keys with numeric values and creates a new Vector3 instance representing the vector described by these values. It's particularly useful for converting serialized vector data back into Vector3 objects, for instance, when loading vectors from a file or a database. + + #### Parameters: + - `data` (dict): A dictionary with keys 'x', 'y', and 'z', corresponding to the components of the vector. Each key's value should be a number (int or float). + + #### Returns: + Vector3: A new Vector3 object initialized with the x, y, and z values from the input dictionary. + + #### Example usage: + ```python + data = {'x': 1.0, 'y': 2.0, 'z': 3.0} + vector = Vector3.deserialize(data) + # Vector3 object with x=1.0, y=2.0, z=3.0 + ``` + """ + return Vector3(data['x'], data['y'], data['z']) + + @staticmethod + def sum(vector_1: 'Vector3', vector_2: 'Vector3') -> 'Vector3': + """Adds two vectors element-wise. + + #### Parameters: + - `vector_1` (Vector3): First vector. + - `vector_2` (Vector3): Second vector. + + Returns: + `Vector3`: Sum of the two input vectors. + + #### Example usage: + + ```python + vector_1 = Vector3(19, 18, 17) + vector_2 = Vector3(8, 17, 1) + output = Vector3.sum(vector_1, vector_2) + # Vector3(X = 27.000, Y = 35.000, Z = 18.000) + ``` + """ + return Vector3( + vector_1.x + vector_2.x, + vector_1.y + vector_2.y, + vector_1.z + vector_2.z + ) + + @staticmethod + def sum3(vector_1: 'Vector3', vector_2: 'Vector3', vector_3: 'Vector3') -> 'Vector3': + """Calculates the sum of three Vector3 objects. + This method returns a new Vector3 object whose components are the sum of the corresponding components of the three input vectors. + + #### Parameters: + - `vector_1`, `vector_2`, `vector_3` (`Vector3`): The vectors to be summed. + + #### Returns: + `Vector3`: A new Vector3 object resulting from the component-wise sum of the input vectors. + + #### Example usage: + ```python + vector1 = Vector3(1, 2, 3) + vector2 = Vector3(4, 5, 6) + vector3 = Vector3(-1, -2, -3) + result = Vector3.sum3(vector1, vector2, vector3) + # Vector3(X = 4.000, Y = 5.000, Z = 6.000) + ``` + """ + return Vector3( + vector_1.x + vector_2.x + vector_3.x, + vector_1.y + vector_2.y + vector_3.y, + vector_1.z + vector_2.z + vector_3.z + ) + + @staticmethod + def diff(vector_1: 'Vector3', vector_2: 'Vector3') -> 'Vector3': + """Calculates the difference between two Vector3 objects. + This method returns a new Vector3 object that is the result of subtracting the components of `vector_2` from `vector_1`. + + #### Parameters: + - `vector_1` (`Vector3`): The minuend vector. + - `vector_2` (`Vector3`): The subtrahend vector. + + #### Returns: + `Vector3`: A new Vector3 object resulting from the component-wise subtraction of `vector_2` from `vector_1`. + + #### Example usage: + ```python + vector1 = Vector3(5, 7, 9) + vector2 = Vector3(1, 2, 3) + result = Vector3.diff(vector1, vector2) + # Vector3(X = 4.000, Y = 5.000, Z = 6.000) + ``` + """ + return Vector3( + vector_1.x - vector_2.x, + vector_1.y - vector_2.y, + vector_1.z - vector_2.z + ) + + @staticmethod + def subtract(vector_1: 'Vector3', vector_2: 'Vector3') -> 'Vector3': + """Subtracts the components of the second vector from the first. + This method is synonymous with `diff` and serves the same purpose, providing an alternative naming convention. + + #### Parameters: + - `vector_1` (`Vector3`): The vector from which to subtract. + - `vector_2` (`Vector3`): The vector to be subtracted. + + #### Returns: + `Vector3`: The result of the component-wise subtraction. + + #### Example usage: + ```python + vector1 = Vector3(10, 20, 30) + vector2 = Vector3(1, 2, 3) + result = Vector3.subtract(vector1, vector2) + # Vector3(X = 9.000, Y = 18.000, Z = 27.000) + ``` + """ + return Vector3( + vector_1.x - vector_2.x, + vector_1.y - vector_2.y, + vector_1.z - vector_2.z + ) + + @staticmethod + def divide(vector_1: 'Vector3', vector_2: 'Vector3') -> 'Vector3': + """Divides the components of the first vector by the corresponding components of the second vector. + This method performs component-wise division. If any component of `vector_2` is 0, the result for that component will be undefined. + + #### Parameters: + - `vector_1` (`Vector3`): The numerator vector. + - `vector_2` (`Vector3`): The denominator vector. + + #### Returns: + `Vector3`: A new Vector3 object resulting from the component-wise division. + + #### Example usage: + ```python + vector1 = Vector3(10, 20, 30) + vector2 = Vector3(2, 4, 5) + result = Vector3.divide(vector1, vector2) + # Vector3(X = 5.000, Y = 5.000, Z = 6.000) + ``` + """ + return Vector3( + vector_1.x / vector_2.x, + vector_1.y / vector_2.y, + vector_1.z / vector_2.z + ) + + @staticmethod + def square(vector_1: 'Vector3') -> 'Vector3': + """ + Computes the square of each component of the input vector. + + #### Parameters: + - `vector_1` (`Vector3`): The input vector. + + #### Returns: + `Vector3`: A new Vector3 object representing the square of each component of the input vector. + + #### Example usage: + ```python + vector = Vector3(2, 3, 4) + squared_vector = Vector3.square(vector) + # Vector3(X = 4, Y = 9, Z = 16) + ``` + """ + return Vector3( + vector_1.x ** 2, + vector_1.y ** 2, + vector_1.z ** 2 + ) + + @staticmethod + def to_point(vector_1: 'Vector3') -> 'Vector3': + """Converts the vector to a Point object. + + #### Parameters: + - `vector_1` (`Vector3`): The vector to be converted to a Point object. + + #### Returns: + `Point`: A Point object with coordinates same as the vector. + + #### Example usage: + ```python + vector1 = Vector3(10, 20, 30) + point = Vector3.to_point(vector1) + # Point(X = 10.000, Y = 20.000, Z = 30.000) + ``` + """ + from geometry.point import Point + return Point(x=vector_1.x, y=vector_1.y, z=vector_1.z) + + @staticmethod + def to_line(vector_1: 'Vector3', vector_2: 'Vector3') -> 'Vector3': + """Creates a Line object from two vectors. + + #### Parameters: + - `vector_1` (`Vector3`): The start vector of the line. + - `vector_2` (`Vector3`): The end vector of the line. + + #### Returns: + `Line`: A Line object connecting the two vectors. + + #### Example usage: + ```python + vector1 = Vector3(10, 20, 30) + vector2 = Vector3(2, 4, 5) + line = Vector3.to_line(vector1, vector2) + # Line(start=Point(X = 10.000, Y = 20.000, Z = 30.000), end=Point(X = 2.000, Y = 4.000, Z = 5.000)) + ``` + """ + from geometry.point import Point + from geometry.curve import Line + return Line(start=Point(x=vector_1.x, y=vector_1.y, z=vector_1.z), end=Point(x=vector_2.x, y=vector_2.y, z=vector_2.z)) + + @staticmethod + def by_line(line_1) -> 'Vector3': + """Computes a vector representing the direction of a given line. + This method takes a Line object and returns a Vector3 representing the direction of the line. + + #### Parameters: + - `line_1` (`Line`): The Line object from which to extract the direction. + + #### Returns: + `Vector3`: A Vector3 representing the direction of the line. + + #### Example usage: + ```python + line = Line(start=Point(0, 0, 0), end=Point(1, 1, 1)) + direction_vector = Vector3.by_line(line) + # Vector3(X = 1, Y = 1, Z = 1) + ``` + """ + return Vector3(line_1.dx, line_1.dy, line_1.dz) + + @staticmethod + def cross_product(vector_1: 'Vector3', vector_2: 'Vector3') -> 'Vector3': + """Computes the cross product of two vectors. + The cross product of two vectors in three-dimensional space is a vector that is perpendicular to both original vectors. It is used to find a vector that is normal to a plane defined by the input vectors. + + #### Parameters: + - `vector_1` (`Vector3`): The first vector. + - `vector_2` (`Vector3`): The second vector. + + #### Returns: + `Vector3`: A new Vector3 object representing the cross product of the input vectors. + + #### Example usage: + ```python + vector1 = Vector3(1, 2, 3) + vector2 = Vector3(4, 5, 6) + cross_product = Vector3.cross_product(vector1, vector2) + # Vector3(X = -3, Y = 6, Z = -3) + ``` + """ + return Vector3( + vector_1.y*vector_2.z - vector_1.z*vector_2.y, + vector_1.z*vector_2.x - vector_1.x*vector_2.z, + vector_1.x*vector_2.y - vector_1.y*vector_2.x + ) + + @staticmethod + def dot_product(vector_1: 'Vector3', vector_2: 'Vector3') -> 'Vector3': + """Computes the dot product of two vectors. + The dot product of two vectors is a scalar quantity equal to the sum of the products of their corresponding components. It gives insight into the angle between the vectors. + + #### Parameters: + - `vector_1` (`Vector3`): The first vector. + - `vector_2` (`Vector3`): The second vector. + + #### Returns: + `float`: The dot product of the input vectors. + + #### Example usage: + ```python + vector1 = Vector3(1, 2, 3) + vector2 = Vector3(4, 5, 6) + dot_product = Vector3.dot_product(vector1, vector2) + # 32 + ``` + """ + return vector_1.x*vector_2.x+vector_1.y*vector_2.y+vector_1.z*vector_2.z + + @staticmethod + def product(number: float, vector_1: 'Vector3') -> 'Vector3': + """Scales a vector by a scalar value. + This method multiplies each component of the vector by the given scalar value. + + #### Parameters: + - `number` (float): The scalar value to scale the vector by. + - `vector_1` (`Vector3`): The vector to be scaled. + + #### Returns: + `Vector3`: A new Vector3 object representing the scaled vector. + + #### Example usage: + ```python + vector1 = Vector3(1, 2, 3) + scaled_vector = Vector3.product(2, vector1) + # Vector3(X = 2, Y = 4, Z = 6) + ``` + """ + return Vector3( + vector_1.x*number, + vector_1.y*number, + vector_1.z*number + ) + + @staticmethod + def length(vector_1: 'Vector3') -> float: + """Computes the length (magnitude) of a vector. + The length of a vector is the Euclidean norm or magnitude of the vector, which is calculated as the square root of the sum of the squares of its components. + + #### Parameters: + - `vector_1` (`Vector3`): The vector whose length is to be computed. + + #### Returns: + `float`: The length of the input vector. + + #### Example usage: + ```python + vector1 = Vector3(1, 2, 3) + length = Vector3.length(vector1) + # 3.7416573867739413 + ``` + """ + return math.sqrt(vector_1.x*vector_1.x+vector_1.y*vector_1.y+vector_1.z*vector_1.z) + + @staticmethod + def pitch(vector_1: 'Vector3', angle: float) -> 'Vector3': + """Rotates a vector around the X-axis (pitch). + This method rotates the vector around the X-axis (pitch) by the specified angle. + + #### Parameters: + - `vector_1` (`Vector3`): The vector to be rotated. + - `angle` (float): The angle of rotation in radians. + + #### Returns: + `Vector3`: A new Vector3 object representing the rotated vector. + + #### Example usage: + ```python + vector1 = Vector3(1, 2, 3) + rotated_vector = Vector3.pitch(vector1, math.pi/2) + # Vector3(X = 1.000, Y = -3.000, Z = 2.000) + ``` + """ + return Vector3( + vector_1.x, + vector_1.y*math.cos(angle) - vector_1.z*math.sin(angle), + vector_1.y*math.sin(angle) + vector_1.z*math.cos(angle) + ) + + @staticmethod + def angle_between(vector_1: 'Vector3', vector_2: 'Vector3') -> float: + """Computes the angle in degrees between two vectors. + The angle between two vectors is the angle required to rotate one vector onto the other, measured in degrees. + + #### Parameters: + - `vector_1` (`Vector3`): The first vector. + - `vector_2` (`Vector3`): The second vector. + + #### Returns: + `float`: The angle in degrees between the input vectors. + + #### Example usage: + ```python + vector1 = Vector3(1, 0, 0) + vector2 = Vector3(0, 1, 0) + angle = Vector3.angle_between(vector1, vector2) + # 90 + ``` + """ + proj_vector_1 = Vector3.to_matrix(vector_1) + proj_vector_2 = Vector3.to_matrix(vector_2) + dot_product = Vector3.dot_product(vector_1, vector_2) + length_vector_1 = Vector3.length(vector_1) + length_vector_2 = Vector3.length(vector_2) + + if length_vector_1 == 0 or length_vector_2 == 0: + return 0 + + cos_angle = dot_product / (length_vector_1 * length_vector_2) + cos_angle = max(-1.0, min(cos_angle, 1.0)) + angle = math.acos(cos_angle) + return math.degrees(angle) + + @staticmethod + def angle_radian_between(vector_1: 'Vector3', vector_2: 'Vector3') -> float: + """Computes the angle in radians between two vectors. + The angle between two vectors is the angle required to rotate one vector onto the other, measured in radians. + + #### Parameters: + - `vector_1` (`Vector3`): The first vector. + - `vector_2` (`Vector3`): The second vector. + + #### Returns: + `float`: The angle in radians between the input vectors. + + #### Example usage: + ```python + vector1 = Vector3(1, 0, 0) + vector2 = Vector3(0, 1, 0) + angle = Vector3.angle_radian_between(vector1, vector2) + # 1.5707963267948966 + ``` + """ + return math.acos((Vector3.dot_product(vector_1, vector_2)/(Vector3.length(vector_1)*Vector3.length(vector_2)))) + + @staticmethod + def angle_between_YZ(vector_1: 'Vector3', vector_2: 'Vector3') -> float: + """Computes the angle in degrees between two vectors projected onto the YZ plane (X-axis rotation). + + #### Parameters: + - `vector_1` (`Vector3`): The first vector. + - `vector_2` (`Vector3`): The second vector. + + #### Returns: + `float`: The angle in degrees between the input vectors projected onto the YZ plane (X-axis rotation). + + #### Example usage: + ```python + vector1 = Vector3(1, 1, 0) + vector2 = Vector3(1, 0, 1) + angle = Vector3.angle_between_YZ(vector1, vector2) + # 90 + ``` + """ + dot_product = Vector3.dot_product(vector_1, vector_2) + length_vector_1 = Vector3.length(Vector3(0, vector_1.y, vector_1.z)) + length_vector_2 = Vector3.length(Vector3(0, vector_2.y, vector_2.z)) + if length_vector_1 == 0 or length_vector_2 == 0: + return 0 + + cos_angle = dot_product / (length_vector_1 * length_vector_2) + cos_angle = max(-1.0, min(cos_angle, 1.0)) + angle = math.acos(cos_angle) + return math.degrees(angle) + + @staticmethod + def angle_between_XZ(vector_1: 'Vector3', vector_2: 'Vector3') -> float: + """Computes the angle in degrees between two vectors projected onto the XZ plane (Y-axis rotation). + + #### Parameters: + - `vector_1` (`Vector3`): The first vector. + - `vector_2` (`Vector3`): The second vector. + + #### Returns: + `float`: The angle in degrees between the input vectors projected onto the XZ plane (Y-axis rotation). + + #### Example usage: + ```python + vector1 = Vector3(1, 0, 1) + vector2 = Vector3(0, 1, 1) + angle = Vector3.angle_between_XZ(vector1, vector2) + # 90 + ``` + """ + dot_product = Vector3.dot_product(vector_1, vector_2) + length_vector_1 = Vector3.length(Vector3(vector_1.x, 0, vector_1.z)) + length_vector_2 = Vector3.length(Vector3(vector_2.x, 0, vector_2.z)) + + if length_vector_1 == 0 or length_vector_2 == 0: + return 0 + + cos_angle = dot_product / (length_vector_1 * length_vector_2) + cos_angle = max(-1.0, min(cos_angle, 1.0)) + angle = math.acos(cos_angle) + return math.degrees(angle) + + @staticmethod + def angle_between_XY(vector_1: 'Vector3', vector_2: 'Vector3') -> float: + """Computes the angle in degrees between two vectors projected onto the XY plane (Z-axis rotation). + + #### Parameters: + - `vector_1` (`Vector3`): The first vector. + - `vector_2` (`Vector3`): The second vector. + + #### Returns: + `float`: The angle in degrees between the input vectors projected onto the XY plane (Z-axis rotation). + + #### Example usage: + ```python + vector1 = Vector3(1, 0, 1) + vector2 = Vector3(0, 1, 1) + angle = Vector3.angle_between_XY(vector1, vector2) + # 45 + ``` + """ + dot_product = Vector3.dot_product(vector_1, vector_2) + length_vector_1 = Vector3.length(Vector3(vector_1.x, vector_1.y, 0)) + length_vector_2 = Vector3.length(Vector3(vector_2.x, vector_2.y, 0)) + + if length_vector_1 == 0 or length_vector_2 == 0: + return 0 + + cos_angle = dot_product / (length_vector_1 * length_vector_2) + cos_angle = max(-1.0, min(cos_angle, 1.0)) + angle = math.acos(cos_angle) + return math.degrees(angle) + + @staticmethod + def value(vector_1: 'Vector3') -> tuple: + """Returns the rounded values of the vector's components. + + #### Parameters: + - `vector_1` (`Vector3`): The vector. + + #### Returns: + `tuple`: A tuple containing the rounded values of the vector's components. + + #### Example usage: + ```python + vector1 = Vector3(1.123456, 2.345678, 3.987654) + rounded_values = Vector3.value(vector1) + # (1.1235, 2.3457, 3.9877) + ``` + """ + roundValue = 4 + return (round(vector_1.x, roundValue), round(vector_1.y, roundValue), round(vector_1.z, roundValue)) + + @staticmethod + def reverse(vector_1: 'Vector3') -> 'Vector3': + """Returns the reverse (negation) of the vector. + + #### Parameters: + - `vector_1` (`Vector3`): The vector. + + #### Returns: + `Vector3`: The reverse (negation) of the input vector. + + #### Example usage: + ```python + vector1 = Vector3(1, 2, 3) + reversed_vector = Vector3.reverse(vector1) + # Vector3(X = -1, Y = -2, Z = -3) + ``` + """ + return Vector3( + vector_1.x*-1, + vector_1.y*-1, + vector_1.z*-1 + ) + + @staticmethod + def perpendicular(vector_1: 'Vector3') -> tuple: + """Computes two vectors perpendicular to the input vector. + + #### Parameters: + - `vector_1` (`Vector3`): The input vector. + + #### Returns: + `tuple`: A tuple containing two vectors perpendicular to the input vector. + + #### Example usage: + ```python + vector1 = Vector3(1, 2, 3) + perpendicular_vectors = Vector3.perpendicular(vector1) + # (Vector3(X = 2, Y = -1, Z = 0), Vector3(X = -3, Y = 0, Z = 1)) + ``` + """ + lokX = Vector3(vector_1.y, -vector_1.x, 0) + lokZ = Vector3.cross_product(vector_1, lokX) + if lokZ.z < 0: + lokZ = Vector3.reverse(lokZ) + return lokX, lokZ + + @staticmethod + def normalize(vector_1: 'Vector3') -> 'Vector3': + """Returns the normalized form of the input vector. + The normalized form of a vector is a vector with the same direction but with a length (magnitude) of 1. + + #### Parameters: + - `vector_1` (`Vector3`): The vector to be normalized. + + #### Returns: + `Vector3`: A new Vector3 object representing the normalized form of the input vector. + + #### Example usage: + ```python + vector1 = Vector3(3, 0, 4) + normalized_vector = Vector3.normalize(vector1) + # Vector3(X = 0.600, Y = 0.000, Z = 0.800) + ``` + """ + length = Vector3.length(vector_1) + if length == 0: + return Vector3(0, 0, 0) + + normalized_vector = Vector3( + vector_1.x / length, + vector_1.y / length, + vector_1.z / length + ) + + return normalized_vector + + @staticmethod + def by_two_points(point_1: 'Point', point_2: 'Point') -> 'Vector3': + """Computes the vector between two points. + + #### Parameters: + - `point_1` (`Point`): The starting point. + - `point_2` (`Point`): The ending point. + + #### Returns: + `Vector3`: A new Vector3 object representing the vector between the two points. + + #### Example usage: + ```python + point1 = Point(1, 2, 3) + point2 = Point(4, 6, 8) + vector = Vector3.by_two_points(point1, point2) + # Vector3(X = 3, Y = 4, Z = 5) + ``` + """ + return Vector3( + point_2.x-point_1.x, + point_2.y-point_1.y, + point_2.z-point_1.z + ) + + @staticmethod + def rotate_XY(vector: 'Vector3', Beta: float) -> 'Vector3': + """Rotates the vector in the XY plane by the specified angle. + + #### Parameters: + - `vector` (`Vector3`): The vector to be rotated. + - `Beta` (float): The angle of rotation in radians. + + #### Returns: + `Vector3`: A new Vector3 object representing the rotated vector. + + #### Example usage: + ```python + vector = Vector3(1, 0, 0) + rotated_vector = Vector3.rotate_XY(vector, math.pi/2) + # Vector3(X = 0, Y = 1, Z = 0) + ``` + """ + return Vector3( + math.cos(Beta)*vector.x - math.sin(Beta)*vector.y, + math.sin(Beta)*vector.x + math.cos(Beta)*vector.y, + vector.z + ) + + @staticmethod + def scale(vector: 'Vector3', scalefactor: float) -> 'Vector3': + """Scales the vector by the specified scale factor. + + #### Parameters: + - `vector` (`Vector3`): The vector to be scaled. + - `scalefactor` (float): The scale factor. + + #### Returns: + `Vector3`: A new Vector3 object representing the scaled vector. + + #### Example usage: + ```python + vector = Vector3(1, 2, 3) + scaled_vector = Vector3.scale(vector, 2) + # Vector3(X = 2, Y = 4, Z = 6) + ``` + """ + return Vector3( + vector.x * scalefactor, + vector.y * scalefactor, + vector.z * scalefactor + ) + + @staticmethod + def new_length(vector_1: 'Vector3', newlength: float) -> 'Vector3': + """Rescales the vector to have the specified length. + + #### Parameters: + - `vector_1` (`Vector3`): The vector to be rescaled. + - `newlength` (float): The desired length of the vector. + + #### Returns: + `Vector3`: A new Vector3 object representing the rescaled vector. + + #### Example usage: + ```python + vector = Vector3(3, 4, 0) + new_vector = Vector3.new_length(vector, 5) + # Vector3(X = 3.000, Y = 4.000, Z = 0.000) + ``` + """ + scale = newlength / Vector3.length(vector_1) + + return Vector3.scale(vector_1, scale) + + @staticmethod + def to_matrix(vector: 'Vector3') -> list: + """Converts the vector to a list representation. + + #### Parameters: + - `vector` (`Vector3`): The vector to be converted. + + #### Returns: + `list`: A list representation of the vector. + + #### Example usage: + ```python + vector = Vector3(1, 2, 3) + vector_list = Vector3.to_matrix(vector) + # [1, 2, 3] + ``` + """ + return [vector.x, vector.y, vector.z] + + @staticmethod + def from_matrix(vector_list: list) -> 'Vector3': + """Creates a Vector3 object from a list representation. + + #### Parameters: + - `vector_list` (list): The list representing the vector. + + #### Returns: + `Vector3`: A Vector3 object created from the list representation. + + #### Example usage: + ```python + vector_list = [1, 2, 3] + vector = Vector3.from_matrix(vector_list) + # Vector3(X = 1, Y = 2, Z = 3) + ``` + """ + return Vector3( + vector_list[0], + vector_list[1], + vector_list[2] + ) + + def __str__(self): + """Returns a string representation of the vector. + + #### Returns: + `str`: A string representation of the vector. + + #### Example usage: + ```python + vector = Vector3(1.234, 2.345, 3.456) + print(vector) + # Vector3(X = 1.234, Y = 2.345, Z = 3.456) + ``` + """ + return f"{__class__.__name__}(" + f"X = {self.x:.3f}, Y = {self.y:.3f}, Z = {self.z:.3f})" + + +X_axis = Vector3(1, 0, 0) + +Y_Axis = Vector3(0, 1, 0) + +Z_Axis = Vector3(0, 0, 1) + + +class Interval: + """The `Interval` class is designed to represent a mathematical interval, providing a start and end value along with functionalities to handle intervals more comprehensively in various applications.""" + def __init__(self, start: float, end: float): + """Initializes a new Interval instance. + + - `start` (float): The starting value of the interval. + - `end` (float): The ending value of the interval. + - `interval` (list, optional): A list that may represent subdivided intervals or specific points within the start and end bounds, depending on the context or method of subdivision. + + """ + self.start = start + self.end = end + self.interval = None + + def serialize(self) -> dict: + """Serializes the interval's attributes into a dictionary. + + This method facilitates converting the interval's properties into a format that can be easily stored or transmitted. + + #### Returns: + dict: A dictionary containing the serialized attributes of the interval. + + #### Example usage: + ```python + + ``` + """ + + return { + 'start': self.start, + 'end': self.end, + 'interval': self.interval + } + + @staticmethod + def deserialize(data: dict) -> 'Interval': + """Reconstructs an Interval object from serialized data contained in a dictionary. + + #### Parameters: + data (dict): The dictionary containing serialized data of an Interval object. + + #### Returns: + Interval: A new Interval object initialized with the data from the dictionary. + + #### Example usage: + ```python + + ``` + """ + + start = data.get('start') + end = data.get('end') + interval = Interval(start, end) + interval.interval = data.get('interval') + return interval + + @classmethod + def by_start_end_count(self, start: float, end: float, count: int) -> 'Interval': + """Generates a list of equidistant points within the interval. + + This method divides the interval between the start and end values into (count - 1) segments, returning an Interval object containing these points. + + #### Parameters: + start (float): The starting value of the interval. + end (float): The ending value of the interval. + count (int): The total number of points to generate, including the start and end values. + + #### Returns: + Interval: An Interval instance with its `interval` attribute populated with the generated points. + + #### Example usage: + ```python + + ``` + """ + intval = [] + numb = start + delta = end-start + for i in range(count): + intval.append(numb) + numb = numb + (delta / (count - 1)) + self.interval = intval + return self + + def __str__(self) -> str: + """Generates a string representation of the Interval. + + #### Returns: + str: A string representation of the Interval, primarily indicating its class name. + + #### Example usage: + ```python + + ``` + """ + return f"{__class__.__name__}" + + +class Plane: + # Plane is an infinite element in space defined by a point and a normal + """The `Plane` class represents an infinite plane in 3D space, defined uniquely by an origin point and a normal vector, along with two other vectors lying on the plane, providing a complete basis for defining plane orientation and position.""" + def __init__(self): + """"Initializes a new Plane instance. + + - `Origin` (Point): The origin point of the plane, which also lies on the plane. + - `Normal` (Vector3): A vector perpendicular to the plane, defining its orientation. + - `v1` (Vector3): A vector lying on the plane, typically representing the "x" direction on the plane. + - `v2` (Vector3): Another vector on the plane, perpendicular to `v1` and typically representing the "y" direction on the plane. + """ + self.Origin = Point(0, 0, 0) + self.Normal = Vector3(x=0, y=0, z=1) + self.vector_1 = Vector3(x=1, y=0, z=0) + self.vector_2 = Vector3(x=0, y=1, z=0) + + def serialize(self) -> dict: + """Serializes the plane's attributes into a dictionary. + This method facilitates the conversion of the plane's properties into a format that can be easily stored or transmitted. + + #### Returns: + dict: A dictionary containing the serialized attributes of the plane. + + #### Example usage: + ```python + + ``` + """ + return { + 'Origin': self.Origin.serialize(), + 'Normal': self.Normal.serialize(), + 'vector_1': self.vector_1.serialize(), + 'vector_2': self.vector_2.serialize() + } + + @staticmethod + def deserialize(data: dict) -> 'Plane': + """Creates a Plane object from a serialized data dictionary. + This method allows for the reconstruction of a Plane instance from data previously serialized into a dictionary format, typically after storage or transmission. + + #### Parameters: + data (dict): The dictionary containing the serialized data of a Plane object. + + #### Returns: + Plane: A new Plane object initialized with the data from the dictionary. + + #### Example usage: + ```python + + ``` + """ + plane = Plane() + plane.Origin = Point.deserialize(data['Origin']) + plane.Normal = Vector3.deserialize(data['Normal']) + plane.vector_1 = Vector3.deserialize(data['vector_1']) + plane.vector_2 = Vector3.deserialize(data['vector_2']) + + return plane + + @classmethod + def by_two_vectors_origin(cls, vector_1: Vector3, vector_2: Vector3, origin: Point) -> 'Plane': + """Creates a Plane defined by two vectors and an origin point. + This method establishes a plane using two vectors that lie on the plane and an origin point. The normal is calculated as the cross product of the two vectors, ensuring it is perpendicular to the plane. + + #### Parameters: + vector_1 (Vector3): The first vector on the plane. + vector_2 (Vector3): The second vector on the plane, should not be parallel to vector_1. + origin (Point): The origin point of the plane, lying on the plane. + + #### Returns: + Plane: A Plane instance defined by the given vectors and origin. + + #### Example usage: + ```python + + ``` + """ + p1 = Plane() + p1.Normal = Vector3.normalize(Vector3.cross_product(vector_1, vector_2)) + p1.Origin = origin + p1.vector_1 = vector_1 + p1.vector_2 = vector_2 + return p1 + + def __str__(self) -> str: + """Generates a string representation of the Plane. + + #### Returns: + str: A string describing the Plane with its origin, normal, and basis vectors. + + #### Example usage: + ```python + + ``` + """ + + return f"{__class__.__name__}(" + f"{self.Origin}, {self.Normal}, {self.vector_1}, {self.vector_2})" + + # TODO + # byLineAndPoint + # byOriginNormal + # byThreePoints + +#intersect class? +def perp(a): + """Calculates a perpendicular vector to the given vector `a`. + This function calculates a vector that is perpendicular to the given 2D vector `a`. The returned vector is in the 2D plane and rotated 90 degrees counterclockwise. + + #### Parameters: + - `a` (list[float]): A 2D vector represented as a list of two floats, `[x, y]`. + + #### Returns: + `list[float]`: A new 2D vector that is perpendicular to `a`. + + #### Example usage: + ```python + vector_a = [1, 0] + perp_vector = perp(vector_a) + # Expected output: [0, 1], representing a vector perpendicular to `vector_a`. + ``` + """ + b = [None] * len(a) + b[0] = -a[1] + b[1] = a[0] + return b + + +def get_line_intersect(line_1: 'Line2D', line_2: 'Line2D') -> 'Point2D': + """Calculates the intersection point of two lines if they intersect. + This function computes the intersection point of two lines defined by `line_1` and `line_2`. If the lines are parallel or coincide, the function returns `None`. + + #### Parameters: + - `line_1` (Line2D): The first line, represented by its start and end points. + - `line_2` (Line2D): The second line, represented by its start and end points. + + #### Returns: + `Point2D` or `None`: The intersection point of `line_1` and `line_2` if they intersect; otherwise, `None`. + + #### Example usage: + ```python + line_1 = Line2D(Point2D(0, 0), Point2D(1, 1)) + line_2 = Line2D(Point2D(1, 0), Point2D(0, 1)) + intersection = get_line_intersect(line_1, line_2) + # Expected output: Point2D(0.5, 0.5), the intersection point. + ``` + """ + + if line_1.start == line_1.end or line_2.start == line_2.end: + return None + + p1, p2 = line_1.start, line_1.end + p1X, p1Y = p1.x, p1.y + p2X, p2Y = p2.x, p2.y + + p3, p4 = line_2.start, line_2.end + p3X, p3Y = p3.x, p3.y + p4X, p4Y = p4.x, p4.y + + da = [p2X - p1X, p2Y - p1Y] + db = [p4X - p3X, p4Y - p3Y] + dp = [p1X - p3X, p1Y - p3Y] + dap = perp(da) + denom = dap[0] * db[0] + dap[1] * db[1] + if abs(denom) < 1e-6: + return None + num = dap[0] * dp[0] + dap[1] * dp[1] + t = num / denom + nX, nY = p3X + t * db[0], p3Y + t * db[1] + + return Point2D(nX, nY) + + +def get_multi_lines_intersect(lines: list[Line2D]) -> list[Point2D]: + """Finds intersection points between multiple Line2D objects. + This function iterates through a list of Line2D objects, calculates intersections between each pair of lines, and returns a list of unique intersection points. If a line does not intersect or the intersection point is not on the line segment, it is ignored. + + #### Parameters: + - `lines` (list[Line2D]): A list of Line2D objects. + + #### Returns: + `list[Point2D]`: A list of Point2D objects representing the intersection points. + + #### Example usage: + ```python + line1 = Line2D(Point2D(0, 0), Point2D(10, 10)) + line2 = Line2D(Point2D(0, 10), Point2D(10, 0)) + lines = [line1, line2] + intersections = get_multi_lines_intersect(lines) + # Expected output: [Point2D(5, 5)], the intersection point of the two lines. + ``` + """ + + pts = [] + for i in range(len(lines)): + line1 = lines[i] + for j in range(i+1, len(lines)): + line2 = lines[j] + intersection = get_line_intersect(line1, line2) + if intersection not in pts and intersection != None and is_point_on_line_segment(intersection, line2) == True: + pts.append(intersection) + return pts + + +def get_intersect_polycurve_lines(polycurve: PolyCurve2D, lines: list[Line2D], split: bool = False, stretch: bool = False): + """Calculates intersections between a PolyCurve2D and multiple Line2D objects, with options to split and stretch lines. + This function identifies intersection points between a PolyCurve2D and a list of Line2D objects. It supports stretching lines to intersect across the entire polycurve and splitting lines at intersection points. The function returns a dictionary containing intersection points, split lines, and categorized lines as either inner or outer grid lines based on their location relative to the polycurve. + + #### Parameters: + - `polycurve` (PolyCurve2D): The PolyCurve2D object to test for intersections. + - `lines` (list[Line2D]): A list of Line2D objects to test for intersections with the polycurve. + - `split` (bool): If True, splits lines at their intersection points. Defaults to False. + - `stretch` (bool): If True, extends lines to calculate intersections across the entire polycurve. Defaults to False. + + #### Returns: + `dict`: A dictionary containing: + - `IntersectGridPoints`: A list of intersection points. + - `SplittedLines`: A list of Line2D objects, split at intersection points if `split` is True. + - `InnerGridLines`: Lines fully contained within the polycurve. + - `OuterGridLines`: Lines extending outside the polycurve. + + #### Example usage: + ```python + polycurve = PolyCurve2D.by_points([Point2D(0, 0), Point2D(10, 0), Point2D(10, 10), Point2D(0, 10)]) + line = Line2D(Point2D(5, -5), Point2D(5, 15)) + results = get_intersect_polycurve_lines(polycurve, [line], split=True) + # Expected output: A dictionary with lists of intersection points, split lines, inner and outer grid lines. + ``` + """ + dict = {} + intersectionsPointsList = [] + splitedLinesList = [] + InnerGridLines = [] + OuterGridLines = [] + if isinstance(lines, Line2D): + lines = [lines] + elif lines.type == "Line": + print("Convert Line(s) to Line2D") + sys.exit() + else: + print(f"Incorrect input: {lines}") + for line in lines: + IntersectGridPoints = [] + for i in range(len(polycurve.points2D) - 1): + genLine = Line2D(polycurve.points2D[i], polycurve.points2D[i+1]) + checkIntersect = get_line_intersect(genLine, line) + if stretch == False: + if checkIntersect != None: + if is_point_on_line_segment(checkIntersect, line) == False: + checkIntersect = None + else: + minX = min( + polycurve.points2D[i].x, polycurve.points2D[i+1].x) + maxX = max( + polycurve.points2D[i].x, polycurve.points2D[i+1].x) + minY = min( + polycurve.points2D[i].y, polycurve.points2D[i+1].y) + maxY = max( + polycurve.points2D[i].y, polycurve.points2D[i+1].y) + if checkIntersect != None: + if minX <= checkIntersect.x <= maxX and minY <= checkIntersect.y <= maxY: + intersectionsPointsList.append(checkIntersect) + IntersectGridPoints.append(checkIntersect) + + elif stretch == True: + minX = min(polycurve.points2D[i].x, polycurve.points2D[i+1].x) + maxX = max(polycurve.points2D[i].x, polycurve.points2D[i+1].x) + minY = min(polycurve.points2D[i].y, polycurve.points2D[i+1].y) + maxY = max(polycurve.points2D[i].y, polycurve.points2D[i+1].y) + if checkIntersect != None: + if minX <= checkIntersect.x <= maxX and minY <= checkIntersect.y <= maxY: + intersectionsPointsList.append(checkIntersect) + IntersectGridPoints.append(checkIntersect) + + if split == True: + if len(IntersectGridPoints) > 0: + splitedLinesList.append(line.split(IntersectGridPoints)) + + for splittedLines in splitedLinesList: + for line in splittedLines: + centerLinePoint = line.point_at_parameter(0.5) + if is_point_in_polycurve(centerLinePoint, polycurve) == True: + InnerGridLines.append(line) + else: + OuterGridLines.append(line) + + dict["IntersectGridPoints"] = intersectionsPointsList + dict["SplittedLines"] = flatten(splitedLinesList) + dict["InnerGridLines"] = InnerGridLines + dict["OuterGridLines"] = OuterGridLines + + return dict + + +def is_point_on_line_segment(point: 'Point2D', line: 'Line2D') -> 'bool': + """Checks if a Point2D is on a Line2D segment. + This function determines whether a given Point2D lies on a specified Line2D segment. It checks if the point is within the line segment's bounding box and calculates its distance from the line to verify its presence on the line. + + #### Parameters: + - `point` (Point2D): The Point2D object to check. + - `line` (Line2D): The Line2D object on which the point's presence is to be checked. + + #### Returns: + `bool`: True if the point lies on the line segment; otherwise, False. + + #### Example usage: + ```python + point = Point2D(5, 5) + line = Line2D(Point2D(0, 0), Point2D(10, 10)) + is_on_segment = is_point_on_line_segment(point, line) + # Expected output: True, since the point lies on the line segment. + ``` + """ + x_min = min(line.start.x, line.end.x) + x_max = max(line.start.x, line.end.x) + y_min = min(line.start.y, line.end.y) + y_max = max(line.start.y, line.end.y) + + if x_min <= point.x <= x_max and y_min <= point.y <= y_max: + try: + distance = abs((line.end.y - line.start.y) * point.x + - (line.end.x - line.start.x) * point.y + + line.end.x * line.start.y + - line.end.y * line.start.x) \ + / line.length + return distance < 1e-9 + except: + return False + return False + + +def get_intersection_polycurve_polycurve(polycurve_1: 'PolyCurve2D', polycurve_2: 'PolyCurve2D') -> 'list[Point2D]': + """Finds intersection points between two PolyCurve2D objects. + This function calculates the intersection points between all line segments of two PolyCurve2D objects. It iterates through each line segment of the first polycurve and checks for intersections with each line segment of the second polycurve. Intersection points that lie on both line segments are added to the result list. + + #### Parameters: + - `polycurve_1` (PolyCurve2D): The first polycurve. + - `polycurve_2` (PolyCurve2D): The second polycurve to intersect with the first one. + + #### Returns: + `list[Point2D]`: A list of Point2D objects representing the intersection points between the two polycurves. + + #### Example usage: + ```python + polycurve1 = PolyCurve2D.by_points([Point2D(0, 0), Point2D(10, 10)]) + polycurve2 = PolyCurve2D.by_points([Point2D(0, 10), Point2D(10, 0)]) + intersections = get_intersection_polycurve_polycurve(polycurve1, polycurve2) + # Expected output: [Point2D(5, 5)], the intersection point of the two polycurves. + ``` + """ + points = [] + for i in range(len(polycurve_1.points2D) - 1): + line1 = Line2D(polycurve_1.points2D[i], polycurve_1.points2D[i+1]) + for j in range(len(polycurve_2.points2D) - 1): + line2 = Line2D(polycurve_2.points2D[j], polycurve_2.points2D[j+1]) + intersection = get_line_intersect(line1, line2) + if intersection and is_point_on_line_segment(intersection, line1) and is_point_on_line_segment(intersection, line2): + points.append(intersection) + return points + + +def split_polycurve_at_intersections(polycurve: 'PolyCurve2D', points: 'list[Point2D]') -> 'list[PolyCurve2D]': + """Splits a PolyCurve2D at specified points and returns the resulting segments as new polycurves. + This function sorts the given intersection points along the direction of the polycurve. Then it iterates through each segment of the polycurve, checking for intersections with the provided points and splitting the polycurve accordingly. Each segment between intersection points becomes a new PolyCurve2D object. + + #### Parameters: + - `polycurve` (PolyCurve2D): The polycurve to be split. + - `points` (list[Point2D]): The points at which to split the polycurve. + + #### Returns: + `list[PolyCurve2D]`: A list of PolyCurve2D objects representing the segments of the original polycurve after splitting. + + #### Example usage: + ```python + polycurve = PolyCurve2D.by_points([Point2D(0, 0), Point2D(5, 5), Point2D(10, 0)]) + points = [Point2D(5, 5)] + split_polycurves = split_polycurve_at_intersections(polycurve, points) + # Expected output: 2 new polycurves, one from (0,0) to (5,5) and another from (5,5) to (10,0). + ``` + """ + points.sort(key=lambda pt: Point2D.distance(polycurve.points2D[0], pt)) + + current_polycurve_points = [polycurve.points2D[0]] + created_polycurves = [] + + for i in range(1, len(polycurve.points2D)): + segment_start = polycurve.points2D[i - 1] + segment_end = polycurve.points2D[i] + + segment_intersections = [pt for pt in points if is_point_on_line_segment( + pt, Line2D(segment_start, segment_end))] + + segment_intersections.sort( + key=lambda pt: Point2D.distance(segment_start, pt)) + + for intersect in segment_intersections: + current_polycurve_points.append(intersect) + created_polycurves.append( + polycurve.by_points(current_polycurve_points)) + current_polycurve_points = [intersect] + points.remove(intersect) + + current_polycurve_points.append(segment_end) + + if len(current_polycurve_points) > 1: + created_polycurves.append( + polycurve.by_points(current_polycurve_points)) + + ptlist = [] + for index, pc in enumerate(created_polycurves): + if index == 0: + for pt in pc.points2D: + ptlist.append(pt) + elif index == 2: + for pt in pc.points2D: + ptlist.append(pt) + ptlist.append(ptlist[1]) + + pcurve = polycurve().by_points(ptlist) + + try: + return [created_polycurves[1], pcurve] + except: + return [created_polycurves[0]] + + +# extend to if on edge, then accept +def is_point_in_polycurve(point: 'Point2D', polycurve: 'PolyCurve2D') -> 'bool': + """Determines if a point is located inside a closed PolyCurve2D. + The function uses a ray-casting algorithm to count the number of times a horizontal ray starting from the given point intersects the polycurve. If the count is odd, the point is inside; if even, the point is outside. + + #### Parameters: + - `point` (Point2D): The point to check. + - `polycurve` (PolyCurve2D): The polycurve to check against. + + #### Returns: + `bool`: True if the point is inside the polycurve, False otherwise. + + #### Example usage: + ```python + polycurve = PolyCurve2D.by_points([Point2D(0, 0), Point2D(10, 0), Point2D(10, 10), Point2D(0, 10)]) + point = Point2D(5, 5) + inside = is_point_in_polycurve(point, polycurve) + # Expected output: True, since the point is inside the polycurve. + ``` + """ + x, y = point.x, point.y + intersections = 0 + for curve in polycurve.curves: + p1, p2 = curve.start, curve.end + if (y > min(p1.y, p2.y)) and (y <= max(p1.y, p2.y)) and (x <= max(p1.x, p2.x)): + if p1.y != p2.y: + x_inters = (y - p1.y) * (p2.x - p1.x) / (p2.y - p1.y) + p1.x + if (p1.x == p2.x) or (x <= x_inters): + intersections += 1 + return intersections % 2 != 0 + + +def is_polycurve_in_polycurve(polycurve_1: 'PolyCurve2D', polycurve_2: 'PolyCurve2D') -> 'bool': + """Checks if all points of one polycurve are inside another polycurve. + Iterates through each point of `polycurve_1` and checks if it is inside `polycurve_2` using the `is_point_in_polycurve` function. If all points of `polycurve_1` are inside `polycurve_2`, the function returns True; otherwise, it returns False. + + #### Parameters: + - `polycurve_1` (PolyCurve2D): The polycurve to check if it is inside `polycurve_2`. + - `polycurve_2` (PolyCurve2D): The polycurve that may contain `polycurve_1`. + + #### Returns: + `bool`: True if all points of `polycurve_1` are inside `polycurve_2`, False otherwise. + + #### Example usage: + ```python + polycurve1 = PolyCurve2D.by_points([Point2D(1, 1), Point2D(2, 2)]) + polycurve2 = PolyCurve2D.by_points([Point2D(0, 0), Point2D(3, 0), Point2D(3, 3), Point2D(0, 3)]) + result = is_polycurve_in_polycurve(polycurve1, polycurve2) + # Expected output: True, since `polycurve1` is entirely within `polycurve2`. + ``` + """ + colList = [] + for pt in polycurve_1.points2D: + if is_point_in_polycurve(pt, polycurve_2): + colList.append(True) + else: + colList.append(False) + + if all_true(colList): + return True + else: + return False + + +def plane_line_intersection(): + """Calculates the intersection point between a plane and a line in 3D space. + Given a line defined by a direction and a point on the line, and a plane defined by a normal vector and a point on the plane, this function calculates the intersection point between the line and the plane, if it exists. + + #### Example usage: + ```python + line_dir = [1, 2, 3] # Direction vector of the line + line_pt = [0, 0, 0] # A point on the line + plane_norm = [4, 5, 6] # Normal vector of the plane + plane_pt = [1, 1, 1] # A point on the plane + intersection_point = plane_line_intersection(line_dir, line_pt, plane_norm, plane_pt) + print("The intersection point is:", intersection_point) + # Output: The intersection point coordinates, if an intersection exists. + ``` + """ + line_dir = [1, 2, 3] + line_pt = [0, 0, 0] + + plane_norm = [4, 5, 6] + plane_pt = [1, 1, 1] + + dot_prod = sum([a*b for a, b in zip(line_dir, plane_norm)]) + + if dot_prod == 0: + print("The line is parallel to the plane. No intersection point.") + else: + t = sum([(a-b)*c for a, b, c in zip(plane_pt, + line_pt, plane_norm)]) / dot_prod + + inter_pt = [a + b*t for a, b in zip(line_pt, line_dir)] + + print("The intersection point is", inter_pt) + + +def split_polycurve_by_points(polycurve: 'PolyCurve2D', points: 'list[Point2D]') -> 'list[PolyCurve2D]': + """Splits a PolyCurve2D at specified points. + Given a list of points, this function splits the input polycurve at these points if they lie on the polycurve. Each segment of the polycurve defined by these points is returned as a new PolyCurve2D. + + #### Parameters: + - `polycurve` (PolyCurve2D): The polycurve to split. + - `points` (list[Point2D]): A list of points at which the polycurve is to be split. + + #### Returns: + `list[PolyCurve2D]`: A list of PolyCurve2D objects representing segments of the original polycurve. + + #### Example usage: + ```python + polycurve = PolyCurve2D.by_points([Point2D(0, 0), Point2D(5, 5), Point2D(10, 0)]) + split_points = [Point2D(5, 5)] + split_polycurves = split_polycurve_by_points(polycurve, split_points) + # Expected output: Two polycurves, one from (0,0) to (5,5) and another from (5,5) to (10,0). + ``` + Note: The provided code snippet for the function does not include an implementation that matches its description. The implementation details need to be adjusted accordingly. + """ + from abstract.intersect2d import is_point_on_line_segment + + def splitCurveAtPoint(curve, point): + if is_point_on_line_segment(point, curve): + return curve.split([point]) + return [curve] + + split_curves = [] + for curve in polycurve.curves: + current_curves = [curve] + for point in points: + new_curves = [] + for c in current_curves: + new_curves.extend(splitCurveAtPoint(c, point)) + current_curves = new_curves + split_curves.extend(current_curves) + + return split_curves + + +def is_on_line(line: 'Line2D', point: 'Point2D') -> 'bool': + """Determines if a given point is on a specified line. + Checks if the given point is exactly at the start or the end point of the line. It does not check if the point lies anywhere else on the line. + + #### Parameters: + - `line` (Line2D): The line to check against. + - `point` (Point2D): The point to check. + + #### Returns: + `bool`: True if the point is exactly at the start or end of the line, False otherwise. + + #### Example usage: + ```python + line = Line2D(Point2D(0, 0), Point2D(10, 10)) + point = Point2D(0, 0) + result = is_on_line(line, point) + # Expected output: True + ``` + Note: This function's implementation appears to contain an error in its condition check. The comparison should directly involve the point with line.start and line.end rather than using `Point2D` class in the condition. + """ + + if line.start == Point2D or line.end == Point2D: + return True + return False + + +def split_polycurve_by_line(polycurve: 'PolyCurve2D', line: 'Line2D') -> 'dict': + """Splits a PolyCurve2D based on its intersection with a Line2D and categorizes the segments. + + This function finds the intersection points between a PolyCurve2D and a Line2D. If exactly two intersection points are found, it splits the PolyCurve2D at these points. The function returns a dictionary containing the original polycurve, the splitted polycurves (if any), and the intersection points. + + #### Parameters: + - `polycurve` (PolyCurve2D): The polycurve to be split. + - `line` (Line2D): The line used to split the polycurve. + + #### Returns: + `dict`: A dictionary with the following keys: + - `inputPolycurve`: The original polycurve. + - `splittedPolycurve`: A list of PolyCurve2D objects representing the splitted segments. + - `nonsplittedPolycurve`: A list containing the original polycurve if no splitting occurred. + - `IntersectGridPoints`: The intersection points between the polycurve and the line. + + #### Example usage: + ```python + polycurve = PolyCurve2D.by_points([Point2D(0, 0), Point2D(5, 5), Point2D(10, 0)]) + line = Line2D(Point2D(0, 5), Point2D(10, 5)) + result_dict = split_polycurve_by_line(polycurve, line) + # Expected output: Dictionary containing splitted polycurves (if any) and intersection points. + ``` + Note: The implementation details provided in the description might not fully align with the actual function code. Adjustments might be needed to ensure the function performs as described. + """ + + dict = {} + pcList = [] + nonsplitted = [] + intersect = get_intersect_polycurve_lines( + polycurve, line, split=False, stretch=False) + intersect_points = intersect["IntersectGridPoints"] + if len(intersect_points) != 2: + nonsplitted.append(polycurve) + dict["inputPolycurve"] = [polycurve] + dict["splittedPolycurve"] = pcList + dict["nonsplittedPolycurve"] = nonsplitted + dict["IntersectGridPoints"] = intersect_points + return dict + + SegsandPoints = [] + + for Line in polycurve.curves: + for intersect_point in intersect_points: + if is_point_on_line_segment(intersect_point, Line): + SegsandPoints.append(intersect_point) + SegsandPoints.append(intersect_point) + + SegsandPoints.append(Line) + + elementen = [] + for item in SegsandPoints: + elementen.append(item) + + split_lists = [] + current_list = [] + + for element in elementen: + current_list.append(element) + + if len(current_list) > 1 and current_list[-1].type == current_list[-2].type == 'Point2D': + split_lists.append(current_list[:-1]) + current_list = [element] + + if current_list: + split_lists.append(current_list) + + merged_list = split_lists[-1] + split_lists[0] + + lijsten = [merged_list, split_lists[1]] + + for lijst in lijsten: + q = [] + for i in lijst: + if i.type == "Line2D": + q.append(i.end) + elif i.type == "Point2D": + q.append(i) + pc = PolyCurve2D.by_points(q) + pcList.append(pc) + + dict["inputPolycurve"] = [polycurve] + dict["splittedPolycurve"] = pcList + dict["nonsplittedPolycurve"] = nonsplitted + dict["IntersectGridPoints"] = intersect_points + + return dict + +class BoundingBox2d: + """Represents a two-dimensional bounding box.""" + def __init__(self): + self.id = generateID() + self.type = __class__.__name__ + self.points = [] + self.corners = [] + self.isClosed = True + self.length = 0 + self.width = 0 + self.z = 0 + + def serialize(self) -> dict: + """Serializes the bounding box's attributes into a dictionary. + + #### Returns: + `dict`: A dictionary containing the serialized attributes of the bounding box. + + #### Example usage: + ```python + bbox2d = BoundingBox2d() + serialized_bbox = bbox2d.serialize() + ``` + """ + id_value = str(self.id) if not isinstance( + self.id, (str, int, float)) else self.id + return { + 'id': id_value, + 'points': self.points, + 'corners': self.corners, + 'isClosed': self.isClosed, + 'length': self.length, + 'width': self.width, + 'z': self.z + } + + @staticmethod + def deserialize(data: dict): + bounding_box = BoundingBox2d() + bounding_box.id = data.get('id') + bounding_box.points = data.get('points', []) + bounding_box.corners = data.get('corners', []) + bounding_box.isClosed = data.get('isClosed', True) + bounding_box.length = data.get('length', 0) + bounding_box.width = data.get('width', 0) + bounding_box.z = data.get('z', 0) + + return bounding_box + + def _length(self): + return 0 + + def area(self): + return 0 + + def by_points(self, points: list[Point]) -> 'BoundingBox2d': + """Constructs a 2D bounding box based on a list of points. + + Calculates the minimum and maximum x and y values from the points to define the corners of the bounding box. + + #### Parameters: + - `points` (list[Point]): A list of Point objects to calculate the bounding box from. + + #### Returns: + `BoundingBox2d`: The bounding box instance with updated corners based on the provided points. + + #### Example usage: + ```python + points = [Point(0, 0, 0), Point(2, 2, 0), Point(2, 0, 0), Point(0, 2, 0)] + bbox = BoundingBox2d().by_points(points) + # BoundingBox2d with corners at (0, 0, 0), (2, 2, 0), (2, 0, 0), and (0, 2, 0) + ``` + """ + + self.points = points + x_values = [point.x for point in self.points] + y_values = [point.y for point in self.points] + + min_x = min(x_values) + max_x = max(x_values) + min_y = min(y_values) + max_y = max(y_values) + + left_top = Point(x=min_x, y=max_y, z=self.z) + left_bottom = Point(x=min_x, y=min_y, z=self.z) + right_top = Point(x=max_x, y=max_y, z=self.z) + right_bottom = Point(x=max_x, y=min_y, z=self.z) + self.length = abs(Point.distance(left_top, left_bottom)) + self.width = abs(Point.distance(left_top, right_top)) + self.corners.append(left_top) + self.corners.append(left_bottom) + self.corners.append(right_bottom) + self.corners.append(right_top) + return self + + def by_dimensions(self, length: float, width: float) -> 'BoundingBox2d': + """Constructs a 2D bounding box with specified dimensions, centered at the origin. + + #### Parameters: + - `length` (float): The length of the bounding box. + - `width` (float): The width of the bounding box. + + #### Returns: + `BoundingBox2d`: The bounding box instance with dimensions centered at the origin. + + #### Example usage: + ```python + bbox = BoundingBox2d().by_dimensions(length=100, width=50) + # BoundingBox2d centered at origin with specified length and width + ``` + """ + + # startpoint = Point2D(0,0) + # widthpoint = Point2D(0,width) + # widthlengthpoint = Point2D(length,width) + # lengthpoint = Point2D(length,0) + + half_length = length / 2 + half_width = width / 2 + + startpoint = Point2D(-half_length, -half_width) + widthpoint = Point2D(-half_length, half_width) + widthlengthpoint = Point2D(half_length, half_width) + lengthpoint = Point2D(half_length, -half_width) + + self.points.append(startpoint) + self.corners.append(startpoint) + self.points.append(widthpoint) + self.corners.append(widthpoint) + self.points.append(widthlengthpoint) + self.corners.append(widthlengthpoint) + self.points.append(lengthpoint) + self.corners.append(lengthpoint) + + self.length = length + self.width = width + return self + + +class BoundingBox3d: + def __init__(self, points=Point): + self.id = generateID() + self.type = __class__.__name__ + self.points = points + self.boundingbox2d = None + self.coordinatesystem = None + self.height = None + + def serialize(self): + id_value = str(self.id) if not isinstance( + self.id, (str, int, float)) else self.id + return { + 'id': id_value, + 'type': self.type, + 'points': [point.serialize() for point in self.points] + } + + @staticmethod + def deserialize(data): + points = [Point.deserialize(point_data) + for point_data in data.get('points', [])] + return BoundingBox3d(points) + + def corners(self) -> 'list[Point]': + """Calculates the eight corners of the 3D bounding box based on the contained points. + + #### Returns: + `list[Point]`: A list of eight Point objects representing the corners of the bounding box. + + #### Example usage: + ```python + bbox3d = BoundingBox3d(points=[Point(1, 1, 1), Point(2, 2, 2)]) + corners = bbox3d.corners() + # Returns a list of eight points representing the corners of the bounding box + ``` + """ + + x_values = [point.x for point in self.points] + y_values = [point.y for point in self.points] + z_values = [point.z for point in self.points] + + min_x = min(x_values) + max_x = max(x_values) + min_y = min(y_values) + max_y = max(y_values) + min_z = min(z_values) + max_z = max(z_values) + + left_top_bottom = Point(x=min_x, y=max_y, z=min_z) + left_bottom_bottom = Point(x=min_x, y=min_y, z=min_z) + right_top_bottom = Point(x=max_x, y=max_y, z=min_z) + right_bottom_bottom = Point(x=max_x, y=min_y, z=min_z) + + left_top_top = Point(x=min_x, y=max_y, z=max_z) + left_bottom_top = Point(x=min_x, y=min_y, z=max_z) + right_top_top = Point(x=max_x, y=max_y, z=max_z) + right_bottom_top = Point(x=max_x, y=min_y, z=max_z) + + return [left_top_bottom, left_top_top, right_top_top, right_top_bottom, left_top_bottom, left_bottom_bottom, left_bottom_top, left_top_top, left_bottom_top, right_bottom_top, right_bottom_bottom, left_bottom_bottom, right_bottom_bottom, right_top_bottom, right_top_top, right_bottom_top] + + def perimeter(self): + return PolyCurve.by_points(self.corners(self.points)) + + def convert_boundingbox_2d(self, boundingbox2d: 'BoundingBox2d', coordinatesystem: 'CoordinateSystem', height: 'float') -> 'BoundingBox3d': + """Converts a 2D bounding box into a 3D bounding box using a specified coordinate system and height. + + #### Parameters: + - `boundingbox2d` (BoundingBox2d): The 2D bounding box to be converted. + - `coordinatesystem` (CoordinateSystem): The coordinate system for the 3D bounding box. + - `height` (float): The height of the 3D bounding box. + + #### Returns: + `BoundingBox3d`: The instance itself, now representing a 3D bounding box. + + #### Example usage: + ```python + bbox2d = BoundingBox2d().by_dimensions(length=100, width=50) + cs = CoordinateSystem() + bbox3d = BoundingBox3d().convert_boundingbox_2d(bbox2d, cs, height=30) + # Converts bbox2d into a 3D bounding box with a specified height and coordinate system + ``` + """ + self.boundingbox2d = boundingbox2d + self.coordinatesystem = coordinatesystem + self.height = height + return self + + def to_cuboid(self) -> 'Extrusion': + """Generates an extrusion representing a cuboid from the 3D bounding box dimensions. + + #### Returns: + `Extrusion`: An Extrusion object that represents a cuboid, matching the dimensions and orientation of the bounding box. + + #### Example usage: + ```python + bbox2d = BoundingBox2d().by_dimensions(length=100, width=50) + cs = CoordinateSystem() + bbox3d = BoundingBox3d().convert_boundingbox_2d(bbox2d, cs, height=30) + cuboid = bbox3d.to_cuboid() + # Generates a cuboid extrusion based on the 3D bounding box + ``` + """ + pts = self.boundingbox2d.corners + pc = PolyCurve2D.by_points(pts) + height = self.height + cs = self.coordinatesystem + dirXvector = Vector3.angle_between(CSGlobal.Y_axis, cs.Y_axis) + pcrot = pc.rotate(dirXvector) # bug multi direction + cuboid = Extrusion.by_polycurve_height_vector( + pcrot, height, CSGlobal, cs.Origin, cs.Z_axis) + return cuboid + + def to_axis(self, length: 'float' = 1000) -> 'list': + """Generates lines representing the coordinate axes of the bounding box's coordinate system. + + This method creates three Line objects corresponding to the X, Y, and Z axes of the bounding box's coordinate system. Each line extends from the origin of the coordinate system in the direction of the respective axis. + + #### Parameters: + - `length` (float, optional): The length of each axis line. Defaults to 1000 units. + + #### Returns: + `list`: A list containing three Line objects representing the X, Y, and Z axes. + + #### Example usage: + ```python + # Assuming `bbox3d` is an instance of BoundingBox3d with a defined coordinate system + axes_lines = bbox3d.to_axis(length=500) + # This returns a list of three Line objects representing the X, Y, and Z axes of the bounding box's coordinate system, each 500 units long. + ``` + """ + if length == None: + length = 1000 + cs = self.coordinatesystem + lnX = Line.by_startpoint_direction_length(cs.Origin, cs.Xaxis, length) + lnY = Line.by_startpoint_direction_length(cs.Origin, cs.Y_axis, length) + lnZ = Line.by_startpoint_direction_length(cs.Origin, cs.Z_axis, length) + return [lnX, lnY, lnZ] + + +class Text: + """The `Text` class is designed to represent and manipulate text within a coordinate system, allowing for the creation of text objects with specific fonts, sizes, and positions. It is capable of generating and translating text into a series of geometric representations.""" + def __init__(self, text: str = None, font_family: 'str' = None, cs='CoordinateSystem', height=None) -> "Text": + """Initializes a new Text instance + + - `id` (str): A unique identifier for the text object. + - `type` (str): The class name, "Text". + - `text` (str, optional): The text string to be represented. + - `font_family` (str, optional): The font family of the text, defaulting to "Arial". + - `xyz` (Vector3): The origin point of the text in the coordinate system. + - `csglobal` (CoordinateSystem): The global coordinate system applied to the text. + - `x`, `y`, `z` (float): The position offsets for the text within its coordinate system. + - `scale` (float, optional): The scale factor applied to the text size. + - `height` (float, optional): The height of the text characters. + - `bbHeight` (float, optional): The bounding box height of the text. + - `width` (float, optional): The calculated width of the text string. + - `character_offset` (int): The offset between characters. + - `space` (int): The space between words. + - `curves` (list): A list of curves representing the text geometry. + - `points` (list): A list of points derived from the text geometry. + - `path_list` (list): A list containing the path data for each character. + """ + self.id = generateID() + self.type = __class__.__name__ + self.text = text + self.font_family = font_family or "arial" + self.xyz = cs.Origin + self.csglobal = cs + self.x, self.y, self.z = 0, 0, 0 + self.scale = None + self.height = height or project.font_height + self.bbHeight = None + self.width = None + self.character_offset = 150 + self.space = 850 + self.curves = [] + self.points = [] + self.path_list = self.load_path() + self.load_o_example = self.load_o() + + def serialize(self) -> 'dict': + """Serializes the text object's attributes into a dictionary. + This method is useful for exporting the text object's properties, making it easier to save or transmit as JSON. + + #### Returns: + dict: A dictionary containing the serialized attributes of the text object. + + #### Example usage: + ```python + + ``` + """ + id_value = str(self.id) if not isinstance( + self.id, (str, int, float)) else self.id + return { + 'id': id_value, + 'type': self.type, + 'text': self.text, + 'font_family': self.font_family, + 'xyz': self.xyz, + 'csglobal': self.csglobal.serialize(), + 'x': self.x, + 'y': self.y, + 'z': self.z, + 'scale': self.scale, + 'height': self.height, + 'bbHeight': self.bbHeight, + 'width': self.width, + 'character_offset': self.character_offset, + 'space': self.space, + 'curves': [curve.serialize() for curve in self.curves], + 'points': self.points, + 'path_list': self.path_list, + } + + def load_path(self) -> 'str': + """Loads the glyph paths for the specified text from a JSON file. + This method fetches the glyph paths for each character in the text attribute, using a predefined font JSON file. + + #### Returns: + str: A string representation of the glyph paths for the text. + + #### Example usage: + ```python + + ``` + """ + with open('C:/Users/mikev/Downloads/building.py3/building.py/library/text/json/Calibri.json', 'r', encoding='utf-8') as file: + response = file.read() + glyph_data = json.loads(response) + output = [] + for letter in self.text: + if letter in glyph_data: + output.append(glyph_data[letter]["glyph-path"]) + elif letter == " ": + output.append("space") + return output + + def load_o(self) -> 'str': + """Loads the glyph paths for the specified text from a JSON file. + This method fetches the glyph paths for each character in the text attribute, using a predefined font JSON file. + + #### Returns: + str: A string representation of the glyph paths for the text. + + #### Example usage: + ```python + + ``` + """ + with open('C:/Users/mikev/Downloads/building.py3/building.py/library/text/json/Calibri.json', 'r', encoding='utf-8') as file: + response = file.read() + glyph_data = json.loads(response) + load_o = [] + letter = "o" + if letter in glyph_data: + load_o.append(glyph_data[letter]["glyph-path"]) + return load_o + + def write(self) -> 'List[List[PolyCurve]]': + """Generates a list of PolyCurve objects representing the text. + Transforms the text into geometric representations based on the specified font, scale, and position. + + #### Returns: + List[List[PolyCurve]]: A list of lists containing PolyCurve objects representing the text geometry. + + #### Example usage: + ```python + + ``` + """ + # start ref_symbol + path = self.load_o_example + ref_points = [] + ref_allPoints = [] + for segment in path: + pathx = parse_path(segment) + for segment in pathx: + segment_type = segment.__class__.__name__ + if segment_type == 'Line': + ref_points.extend( + [(segment.start.real, segment.start.imag), (segment.end.real, segment.end.imag)]) + ref_allPoints.extend( + [(segment.start.real, segment.start.imag), (segment.end.real, segment.end.imag)]) + elif segment_type == 'CubicBezier': + ref_points.extend(segment.sample(10)) + ref_allPoints.extend(segment.sample(10)) + elif segment_type == 'QuadraticBezier': + for i in range(11): + t = i / 10.0 + point = segment.point(t) + ref_points.append((point.real, point.imag)) + ref_allPoints.append((point.real, point.imag)) + elif segment_type == 'Arc': + ref_points.extend(segment.sample(10)) + ref_allPoints.extend(segment.sample(10)) + height = self.calculate_bounding_box(ref_allPoints)[2] + self.scale = self.height / height + # end ref_symbol + + output_list = [] + for letter_path in self.path_list: + points = [] + allPoints = [] + if letter_path == "space": + self.x += self.space + self.character_offset + pass + else: + path = parse_path(letter_path) + for segment in path: + segment_type = segment.__class__.__name__ + if segment_type == 'Move': + if len(points) > 0: + points = [] + allPoints.append("M") + subpath_started = True + elif subpath_started: + if segment_type == 'Line': + points.extend( + [(segment.start.real, segment.start.imag), (segment.end.real, segment.end.imag)]) + allPoints.extend( + [(segment.start.real, segment.start.imag), (segment.end.real, segment.end.imag)]) + elif segment_type == 'CubicBezier': + points.extend(segment.sample(10)) + allPoints.extend(segment.sample(10)) + elif segment_type == 'QuadraticBezier': + for i in range(11): + t = i / 10.0 + point = segment.point(t) + points.append((point.real, point.imag)) + allPoints.append((point.real, point.imag)) + elif segment_type == 'Arc': + points.extend(segment.sample(10)) + allPoints.extend(segment.sample(10)) + if points: + output_list.append( + self.convert_points_to_polyline(allPoints)) + width = self.calculate_bounding_box(allPoints)[1] + self.x += width + self.character_offset + + height = self.calculate_bounding_box(allPoints)[2] + self.bbHeight = height + pList = [] + for ply in flatten(output_list): + translated = self.translate(ply) + pList.append(translated) + + for pl in pList: + for pt in pl.points: + self.points.append(pt) + + # print(f'Object text naar objects gestuurd.') + return pList + + def translate(self, polyCurve: 'PolyCurve') -> 'PolyCurve': + """Translates a PolyCurve according to the text object's global coordinate system and scale. + + #### Parameters: + polyCurve (PolyCurve): The PolyCurve to be translated. + + #### Returns: + PolyCurve: The translated PolyCurve. + + #### Example usage: + ```python + + ``` + """ + trans = [] + for pt in polyCurve.points: + pscale = Point.product(self.scale, pt) + pNew = transform_point_2(pscale, self.csglobal) + trans.append(pNew) + return polyCurve.by_points(trans) + + def calculate_bounding_box(self, points: 'list[Point]') -> tuple: + """Calculates the bounding box for a given set of points. + + #### Parameters: + points (list): A list of points to calculate the bounding box for. + + #### Returns: + tuple: A tuple containing the bounding box, its width, and its height. + + #### Example usage: + ```python + + ``` + """ + + points = [elem for elem in points if elem != 'M'] + ptList = [Point2D(pt[0], pt[1]) for pt in points] + bounding_box_polyline = BoundingBox2d().by_points(ptList) + return bounding_box_polyline, bounding_box_polyline.width, bounding_box_polyline.length + + def convert_points_to_polyline(self, points: 'list[Point]') -> 'PolyCurve': + """Converts a list of points into a PolyCurve. + This method is used to generate a PolyCurve from a series of points, typically derived from text path data. + + #### Parameters: + points (list): A list of points to be converted into a PolyCurve. + + #### Returns: + PolyCurve: A PolyCurve object representing the points. + + #### Example usage: + ```python + + ``` + """ + output_list = [] + sub_lists = [[]] + tempPoints = [elem for elem in points if elem != 'M'] + x_values = [point[0] for point in tempPoints] + y_values = [point[1] for point in tempPoints] + + xmin = min(x_values) + ymin = min(y_values) + + for item in points: + if item == 'M': + sub_lists.append([]) + else: + x = item[0] + self.x - xmin + y = item[1] + self.y - ymin + z = self.xyz.z + eput = x, y, z + sub_lists[-1].append(eput) + output_list = [[Point(point[0], point[1], self.xyz.z) + for point in element] for element in sub_lists] + + polyline_list = [ + PolyCurve.by_points( + [Point(coord.x, coord.y, self.xyz.z) for coord in pts]) + for pts in output_list + ] + return polyline_list + + +class Geometry: + def Translate(object, v): + if object.type == 'Point': + p1 = Point.to_matrix(object) + v1 = Vector3.to_matrix(v) + + ar1 = Point.to_matrix(p1) + ar2 = Vector3.to_matrix(v1) + + c = [ar1[i] + ar2[i] for i in range(len(ar1))] + + return Point(c[0], c[1], c[2]) + + elif object.type == 'Line': + return Line(Geometry.Translate(object.start, v), (Geometry.Translate(object.end, v))) + + elif object.type == "PolyCurve": + translated_points = [] + + # Extract the direction components from the Vector3 object + direction_x, direction_y, direction_z = v.x, v.y, v.z + + for point in object.points: + p1 = Point.to_matrix(point) + # Apply the translation + c = [p1[0] + direction_x, p1[1] + + direction_y, p1[2] + direction_z] + + translated_points.append(Point(c[0], c[1], c[2])) + + return PolyCurve.by_points(translated_points) + else: + print(f"[translate] '{object.type}' object is not added yet") +class BuildingPy: + def __init__(self, name=None, number=None): + self.name: str = name + self.number: str = number + self.debug: bool = True + self.objects = [] + self.units = "mm" + self.decimals = 3 #not fully implemented yet + self.origin = Point(0,0,0) + self.default_font = "calibri" + self.scale = 1 + self.font_height = 500 + self.repr_round = 3 + #prefix objects (name) + #Geometry settings + + #export selection info + self.domain = None + self.applicationId = "OPEN-AEC BuildingPy" + + #different settings for company's? + + #rename this to autoclose? + self.closed: bool = True #auto close polygons? By default true, else overwrite + self.round: bool = False #If True then arcs will be segmented. Can be used in Speckle. + + #functie polycurve of iets van een class/def + self.autoclose: bool = True #new self.closed + + #nodes + self.node_merge = True #False not yet created + self.node_diameter = 250 + self.node_threshold = 50 + + #text + self.createdTxt = "has been created" + + #structural elements + self.structural_fallback_element = "HEA100" + + #Speckle settings + self.speckleserver = "speckle.xyz" + self.specklestream = None + + #FreeCAD settings + + X_axis = Vector3(1, 0, 0) + Y_Axis = Vector3(0, 1, 0) + Z_Axis = Vector3(0, 0, 1) + self.CSGlobal = CoordinateSystem(Point(0, 0, 0), X_axis, Y_Axis, Z_Axis) + + def save(self): + # print(self.objects) + serialized_objects = [] + for obj in self.objects: + try: + # print(obj) + serialized_objects.append(json.dumps(obj.serialize())) + except: + print(obj) + + serialized_data = json.dumps(serialized_objects) + file_name = 'project/data.json' + with open(file_name, 'w') as file: + file.write(serialized_data) + + + type_count = defaultdict(int) + for serialized_item in serialized_objects: + item = json.loads(serialized_item) + item_type = item.get("type") + if item_type: + type_count[item_type] += 1 + + total_items = len(serialized_objects) + + print(f"\nTotal saved items to '{file_name}': {total_items}") + print("Type counts:") + for item_type, count in type_count.items(): + print(f"{item_type}: {count}") + + def open(self): + pass # open data.json objects in here + + def toSpeckle(self, streamid, commitstring=None): + from exchange.speckle import translateObjectsToSpeckleObjects, TransportToSpeckle + self.specklestream = streamid + speckleobj = translateObjectsToSpeckleObjects(self.objects) + TransportToSpeckle(self.speckleserver, streamid, speckleobj, commitstring) + + def toFreeCAD(self): + from exchange.Freecad_Bupy import translateObjectsToFreeCAD + translateObjectsToFreeCAD(self.objects) + + def toIFC(self, name): + from exchange.IFC import translateObjectsToIFC, CreateIFC + ifc_project = CreateIFC() + ifc_project.add_project(name) + ifc_project.add_site("My Site") + ifc_project.add_building("Building A") + ifc_project.add_storey("Ground Floor") + ifc_project.add_storey("G2Floor") + translateObjectsToIFC(self.objects, ifc_project) + ifc_project.export(f"{name}.ifc") +# [!not included in BP singlefile - end] + + +project = BuildingPy("Project", "0") + + +class TickMark: + # Dimension Tick Mark + def __init__(self): + self.name = None + self.id = generateID() + self.curves = [] + + @staticmethod + def by_curves(name, curves): + TM = TickMark() + TM.name = name + TM.curves = curves + return TM + + +TMDiagonal = TickMark.by_curves( + "diagonal", [Line(start=Point(-100, -100, 0), end=Point(100, 100, 0))]) + + +class DimensionType: + def __init__(self): + self.name = None + self.id = generateID() + self.font = None + self.text_height = 2.5 + self.tick_mark: TickMark = TMDiagonal + self.line_extension = 100 + + def serialize(self): + return { + 'name': self.name, + 'id': self.id, + 'type': self.type, + 'font': self.font, + 'text_height': self.text_height, + 'tick_mark': str(self.tick_mark), + 'line_extension': self.line_extension + } + + @staticmethod + def deserialize(data): + dimension_type = DimensionType() + dimension_type.name = data.get('name') + dimension_type.id = data.get('id') + dimension_type.type = data.get('type') + dimension_type.font = data.get('font') + dimension_type.text_height = data.get('text_height', 2.5) + + # Handle TickMark deserialization + tick_mark_str = data.get('tick_mark') + # Adjust according to your TickMark implementation + dimension_type.tick_mark = TickMark(tick_mark_str) + + dimension_type.line_extension = data.get('line_extension', 100) + + return dimension_type + + @staticmethod + def by_name_font_textheight_tick_mark_extension(name: str, font: str, text_height: float, tick_mark: TickMark, line_extension: float): + DT = DimensionType() + DT.name = name + DT.font = font + DT.text_height = text_height + DT.tick_mark = tick_mark + DT.line_extension = line_extension + return DT + + +DT2_5_mm = DimensionType.by_name_font_textheight_tick_mark_extension( + "2.5 mm", "calibri", 2.5, TMDiagonal, 100) + +DT1_8_mm = DimensionType.by_name_font_textheight_tick_mark_extension( + "1.8 mm", "calibri", 2.5, TMDiagonal, 100) + + +class Dimension: + def __init__(self, start: Point, end: Point, dimension_type) -> None: + self.id = generateID() + self.start: Point = start + self.text_height = 100 + self.end: Point = end + self.scale = 0.1 # text + self.dimension_type: DimensionType = dimension_type + self.curves = [] + self.length: float = Line(start=self.start, end=self.end).length + self.text = None + self.geom() + + def serialize(self): + return { + 'type': self.type, + 'start': self.start.serialize(), + 'end': self.end.serialize(), + 'text_height': self.text_height, + 'id': self.id, + 'scale': self.scale, + 'dimension_type': self.dimension_type.serialize(), + 'curves': [curve.serialize() for curve in self.curves], + 'length': self.length, + 'text': self.text + } + + @staticmethod + def deserialize(data): + start = Point.deserialize(data['start']) + end = Point.deserialize(data['end']) + dimension_type = DimensionType.deserialize(data['dimension_type']) + dimension = Dimension(start, end, dimension_type) + + dimension.text_height = data.get('text_height', 100) + dimension.id = data.get('id') + dimension.scale = data.get('scale', 0.1) + dimension.curves = [Line.deserialize( + curve_data) for curve_data in data.get('curves', [])] + dimension.length = data.get('length') + dimension.text = data.get('text') + + return dimension + + @staticmethod + def by_startpoint_endpoint_offset(start: Point, end: Point, dimension_type: DimensionType, offset: float): + DS = Dimension() + DS.start = start + DS.end = end + DS.dimension_type = dimension_type + DS.geom() + return DS + + def geom(self): + # baseline + baseline = Line(start=self.start, end=self.end) + midpoint_text = baseline.mid_point() + direction = Vector.normalize(baseline.vector) + tick_mark_extension_point_1 = Point.translate(self.start, Vector.reverse( + Vector.scale(direction, self.dimension_type.line_extension))) + tick_mark_extension_point_2 = Point.translate( + self.end, Vector.scale(direction, self.dimension_type.line_extension)) + x = direction + y = Vector.rotate_XY(x, math.radians(90)) + z = Z_Axis + cs_new_start = CoordinateSystem(self.start, x, y, z) + cs_new_mid = CoordinateSystem(midpoint_text, x, y, z) + cs_new_end = CoordinateSystem(self.end, x, y, z) + self.curves.append(Line(tick_mark_extension_point_1, + self.start)) # extention_start + self.curves.append( + Line(tick_mark_extension_point_2, self.end)) # extention_end + self.curves.append(Line(self.start, self.end)) # baseline + # erg vieze oplossing. #Todo + crvs = Line( + start=self.dimension_type.tick_mark.curves[0].start, end=self.dimension_type.tick_mark.curves[0].end) + + self.curves.append(Line.transform( + self.dimension_type.tick_mark.curves[0], cs_new_start)) # dimension tick start + self.curves.append(Line.transform(crvs, cs_new_end) + ) # dimension tick end + self.text = Text(text=str(round(self.length)), font_family=self.dimension_type.font, + cs=cs_new_mid, height=self.text_height).write() + + def write(self, project): + for i in self.curves: + project.objects.append(i) + for j in self.text: + project.objects.append(j) + + +class FrameTag: + def __init__(self): + # Dimensions in 1/100 scale + self.id = generateID() + self.scale = 0.1 + self.cs: CoordinateSystem = CSGlobal + self.offset_x = 500 + self.offset_y = 100 + self.font_family = "calibri" + self.text: str = "text" + self.text_curves = None + self.text_height = 100 + + def serialize(self): + id_value = str(self.id) if not isinstance( + self.id, (str, int, float)) else self.id + return { + 'id': id_value, + 'type': self.type, + 'scale': self.scale, + 'cs': self.cs.serialize(), + 'offset_x': self.offset_x, + 'offset_y': self.offset_y, + 'font_family': self.font_family, + 'text': self.text, + 'text_curves': self.text_curves, + 'text_height': self.text_height + } + + @staticmethod + def deserialize(data): + frame_tag = FrameTag() + frame_tag.scale = data.get('scale', 0.1) + frame_tag.cs = CoordinateSystem.deserialize(data['cs']) + frame_tag.offset_x = data.get('offset_x', 500) + frame_tag.offset_y = data.get('offset_y', 100) + frame_tag.font_family = data.get('font_family', "calibri") + frame_tag.text = data.get('text', "text") + frame_tag.text_curves = data.get('text_curves') + frame_tag.text_height = data.get('text_height', 100) + + return frame_tag + + def __textobject(self): + cstext = self.cs + # cstextnew = cstext.translate(self.textoff_vector_local) + self.text_curves = Text( + text=self.text, font_family=self.font_family, height=self.text_height, cs=cstext).write + + def by_cs_text(self, coordinate_system: CoordinateSystem, text): + self.cs = coordinate_system + self.text = text + self.__textobject() + return self + + def write(self, project): + for x in self.text_curves(): + project.objects.append(x) + return self + + @staticmethod + def by_frame(frame): + tag = FrameTag() + frame_vector = frame.vector_normalised + x = frame_vector + y = Vector.rotate_XY(x, math.radians(90)) + z = Z_Axis + vx = Vector.scale(frame_vector, tag.offset_x) + frame_width = PolyCurve2D.bounds(frame.curve)[4] + vy = Vector.scale(y, frame_width*0.5+tag.offset_y) + origintext = Point.translate(frame.start, vx) + origintext = Point.translate(origintext, vy) + csnew = CoordinateSystem(origintext, x, y, z) + tag.cs = csnew + tag.text = frame.name + tag.__textobject() + return tag + + +class ColumnTag: + def __init__(self): + # Dimensions in 1/100 scale + self.id = generateID() + self.width = 700 + self.height = 500 + self.factor = 3 # hellingsfacor leader + self.scale = 0.1 # voor tekeningverschaling + self.position = "TL" # TL, TR, BL, BR Top Left Top Right Bottom Left Bottom Right + self.cs: CoordinateSystem = CSGlobal + + # self.textoff_vector_local: Vector = Vector(1,1,1) + self.font_family = "calibri" + self.curves = [] + # self.leadercurves() + self.text: str = "text" + self.text_height = 100 + self.text_offset_factor = 5 + self.textoff_vector_local: Vector = Vector( + self.height/self.factor, self.height+self.height/self.text_offset_factor, 0) + self.text_curves = None + # self.textobject() + + def serialize(self): + id_value = str(self.id) if not isinstance( + self.id, (str, int, float)) else self.id + return { + 'id': id_value, + 'type': self.type, + 'width': self.width, + 'height': self.height, + 'factor': self.factor, + 'scale': self.scale, + 'position': self.position, + 'cs': self.cs.serialize(), + 'font_family': self.font_family, + 'curves': [curve.serialize() for curve in self.curves], + 'text': self.text, + 'text_height': self.text_height, + 'text_offset_factor': self.text_offset_factor, + 'textoff_vector_local': self.textoff_vector_local.serialize(), + 'text_curves': self.text_curves + } + + @staticmethod + def deserialize(data): + column_tag = ColumnTag() + column_tag.width = data.get('width', 700) + column_tag.height = data.get('height', 500) + column_tag.factor = data.get('factor', 3) + column_tag.scale = data.get('scale', 0.1) + column_tag.position = data.get('position', "TL") + column_tag.cs = CoordinateSystem.deserialize(data['cs']) + column_tag.font_family = data.get('font_family', "calibri") + column_tag.curves = [Line.deserialize( + curve_data) for curve_data in data.get('curves', [])] + column_tag.text = data.get('text', "text") + column_tag.text_height = data.get('text_height', 100) + column_tag.text_offset_factor = data.get('text_offset_factor', 5) + column_tag.textoff_vector_local = Vector.deserialize( + data['textoff_vector_local']) + column_tag.text_curves = data.get('text_curves') + + return column_tag + + def __leadercurves(self): + self.startpoint = Point(0, 0, 0) + self.midpoint = Point.translate(self.startpoint, Vector( + self.height/self.factor, self.height, 0)) + self.endpoint = Point.translate( + self.midpoint, Vector(self.width, 0, 0)) + crves = [Line(start=self.startpoint, end=self.midpoint), + Line(start=self.midpoint, end=self.endpoint)] + for i in crves: + j = Line.transform(i, self.cs) + self.curves.append(j) + + def __textobject(self): + cstext = self.cs + + cstextnew = CoordinateSystem.translate( + cstext, self.textoff_vector_local) + self.text_curves = Text(text=self.text, font_family=self.font_family, + height=self.text_height, cs=cstextnew).write + + def by_cs_text(self, coordinate_system: CoordinateSystem, text): + self.cs = coordinate_system + self.text = text + self.__leadercurves() + self.__textobject() + return self + + def write(self, project): + for x in self.text_curves(): + project.objects.append(x) + for y in self.curves: + project.objects.append(y) + + @staticmethod + def by_frame(frame, position="TL"): + tag = ColumnTag() + csold = CSGlobal + tag.position = position + tag.cs = CoordinateSystem.translate(csold, Vector( + frame.start.x, frame.start.y, frame.start.z)) + tag.text = frame.name + tag.__leadercurves() + tag.__textobject() + return tag + +# class Label: +# class LabelType: +# class TextType: +seqChar = "A B C D E F G H I J K L M N O P Q R S T U V W X Y Z AA AB AC" +seqNumber = "1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24" + + +class GridheadType: + def __init__(self): + self.id = generateID() + self.type = __class__.__name__ + self.name = None + self.curves = [] + self.diameter = 150 + self.text_height = 200 + self.radius = self.diameter/2 + self.font_family = "calibri" + + def serialize(self): + id_value = str(self.id) if not isinstance( + self.id, (str, int, float)) else self.id + return { + 'id': id_value, + 'type': self.type, + 'name': self.name, + 'curves': [curve.serialize() for curve in self.curves], + 'diameter': self.diameter, + 'text_height': self.text_height, + 'radius': self.radius, + 'font_family': self.font_family + } + + @staticmethod + def deserialize(data): + gridhead_type = GridheadType() + gridhead_type.id = data.get('id') + gridhead_type.type = data.get('type') + gridhead_type.name = data.get('name') + gridhead_type.curves = [Line.deserialize(curve_data) for curve_data in data.get( + 'curves', [])] # Adjust for your Curve class + gridhead_type.diameter = data.get('diameter', 150) + gridhead_type.text_height = data.get('text_height', 200) + gridhead_type.radius = data.get('radius', gridhead_type.diameter / 2) + gridhead_type.font_family = data.get('font_family', "calibri") + + return gridhead_type + + def by_diam(self, name, diameter: float, font_family, text_height): + self.name = name + self.diameter = diameter + self.radius = self.diameter / 2 + self.font_family = font_family + self.text_height = text_height + self.geom() + return self + + def geom(self): + radius = self.radius + self.curves.append(Arc(startPoint=Point(-radius, radius, 0), + midPoint=Point(0, radius*2, 0), endPoint=Point(radius, radius, 0))) + self.curves.append(Arc(startPoint=Point(-radius, radius, 0), + midPoint=Point(0, 0, 0), endPoint=Point(radius, radius, 0))) + # origin is at center of circle + + +GHT30 = GridheadType().by_diam("2.5 mm", 400, "calibri", 200) + +GHT50 = GridheadType().by_diam("GHT50", 600, "calibri", 350) + + +class GridHead: + def __init__(self): + self.id = generateID() + self.type = __class__.__name__ + self.grid_name: str = "A" + self.grid_head_type = GHT50 + self.radius = GHT50.radius + self.CS: CoordinateSystem = CSGlobal + self.x: float = 0.5 + self.y: float = 0 + self.text_curves = [] + self.curves = [] + self.__textobject() + self.__geom() + + def serialize(self): + id_value = str(self.id) if not isinstance( + self.id, (str, int, float)) else self.id + return { + 'id': id_value, + 'type': self.type, + 'grid_name': self.grid_name, + 'grid_head_type': self.grid_head_type.serialize(), + 'radius': self.radius, + 'CS': self.CS.serialize(), + 'x': self.x, + 'y': self.y, + 'text_curves': [curve.serialize() for curve in self.text_curves], + 'curves': [curve.serialize() for curve in self.curves] + } + + @staticmethod + def deserialize(data): + grid_head = GridHead() + grid_head.id = data.get('id') + grid_head.type = data.get('type') + grid_head.grid_name = data.get('grid_name') + grid_head.grid_head_type = GridheadType.deserialize( + data['grid_head_type']) + grid_head.radius = data.get('radius', GHT50.radius) + grid_head.CS = CoordinateSystem.deserialize(data['CS']) + grid_head.x = data.get('x', 0.5) + grid_head.y = data.get('y', 0) + grid_head.text_curves = [Line.deserialize( + curve_data) for curve_data in data.get('text_curves', [])] + grid_head.curves = [Line.deserialize( + curve_data) for curve_data in data.get('curves', [])] + + return grid_head + + def __geom(self): + # CStot = CoordinateSystem.translate(self.CS,Vector3(0,self.grid_head_type.radius,0)) + for i in self.grid_head_type.curves: + self.curves.append(transform_arc(i, (self.CS))) + + def __textobject(self): + cs_text = self.CS + # to change after center text function is implemented + cs_text_new = CoordinateSystem.move_local(cs_text, -100, 40, 0) + self.text_curves = Text(text=self.grid_name, font_family=self.grid_head_type.font_family, + height=self.grid_head_type.text_height, cs=cs_text_new).write() + project.objects.append(Text(text=self.grid_name, font_family=self.grid_head_type.font_family, + height=self.grid_head_type.text_height, cs=cs_text_new)) + + @staticmethod + def by_name_gridheadtype_y(name, cs: CoordinateSystem, gridhead_type, y: float): + GH = GridHead() + GH.grid_name = name + GH.grid_head_type = gridhead_type + GH.CS = cs + GH.x = 0.5 + GH.y = y + GH.__textobject() + GH.__geom() + return GH + + def write(self, project): + for x in self.text_curves: + project.objects.append(x) + for y in self.curves: + project.objects.append(y) + + +class Grid: + def __init__(self): + self.line = None + self.start = None + self.end = None + self.direction: Vector3 = Vector3(0, 1, 0) + self.grid_head_type = GHT50 + self.name = None + self.bulbStart = False + self.bulbEnd = True + self.cs_end: CoordinateSystem = CSGlobal + self.grid_heads = [] + + def __cs(self, line): + self.direction = line.vector_normalised + vect3 = Vector3.rotate_XY(self.direction, math.radians(-90)) + self.cs_end = CoordinateSystem(line.end, vect3, self.direction, Z_Axis) + + @classmethod + def by_startpoint_endpoint(cls, line, name): + # Create panel by polycurve + g1 = Grid() + g1.start = line.start + g1.end = line.start + g1.name = name + g1.__cs(line) + g1.line = line_to_pattern(line, Centerline) + g1.__grid_heads() + return g1 + + def __grid_heads(self): + if self.bulbEnd == True: + self.grid_heads.append( + GridHead.by_name_gridheadtype_y(self.name, self.cs_end, self.grid_head_type, 0)) + + def write(self, project): + for x in self.line: + project.objects.append(x) + for y in self.grid_heads: + y.write(project) + return self + + +def get_grid_distances(Grids): + # Function to create grids from the format 0, 4x5400, 4000, 4000 to absolute XYZ-values + GridsNew = [] + GridsNew.append(0) + distance = 0.0 + # GridsNew.append(distance) + for i in Grids: + # del Grids[0] + if "x" in i: + spl = i.split("x") + count = int(spl[0]) + width = float(spl[1]) + for i in range(count): + distance = distance + width + GridsNew.append(distance) + else: + distance = distance + float(i) + GridsNew.append(distance) + return GridsNew + + +class GridSystem: + # rectangle Gridsystem + def __init__(self): + self.id = generateID() + self.type = __class__.__name__ + self.gridsX = None + self.gridsY = None + self.dimensions = [] + self.name = None + + def serialize(self): + id_value = str(self.id) if not isinstance( + self.id, (str, int, float)) else self.id + return { + 'id': id_value, + 'type': self.type, + 'gridsX': self.gridsX, + 'gridsY': self.gridsY, + 'dimensions': [dimension.serialize() for dimension in self.dimensions], + 'name': self.name + } + + @staticmethod + def deserialize(data): + grid_system = GridSystem() + grid_system.id = data.get('id') + grid_system.type = data.get('type') + grid_system.gridsX = data.get('gridsX') + grid_system.gridsY = data.get('gridsY') + grid_system.dimensions = [Dimension.deserialize(dim_data) for dim_data in data.get( + 'dimensions', [])] # Adjust for your Dimension class + grid_system.name = data.get('name') + + return grid_system + + @classmethod + def by_spacing_labels(cls, spacingX, labelsX, spacingY, labelsY, gridExtension): + gs = GridSystem() + # Create gridsystem + # spacingXformat = "0 3000 3000 3000" + GridEx = gridExtension + + GridsX = spacingX.split() + GridsX = get_grid_distances(GridsX) + Xmax = max(GridsX) + GridsXLable = labelsX.split() + GridsY = spacingY.split() + GridsY = get_grid_distances(GridsY) + Ymax = max(GridsY) + GridsYLable = labelsY.split() + + gridsX = [] + dimensions = [] + count = 0 + ymaxdim1 = Ymax+GridEx-300 + ymaxdim2 = Ymax+GridEx-0 + xmaxdim1 = Xmax+GridEx-300 + xmaxdim2 = Xmax+GridEx-0 + for i in GridsX: + gridsX.append(Grid.by_startpoint_endpoint( + Line(Point(i, -GridEx, 0), Point(i, Ymax+GridEx, 0)), GridsXLable[count])) + try: + dim = Dimension(Point(i, ymaxdim1, 0), Point( + GridsX[count+1], ymaxdim1, 0), DT2_5_mm) + gs.dimensions.append(dim) + except: + pass + count = count + 1 + + # Totaal maatvoering 1 + dim = Dimension(Point(GridsX[0], ymaxdim2, 0), Point( + Xmax, ymaxdim2, 0), DT2_5_mm) + gs.dimensions.append(dim) + + # Totaal maatvoering 2 + dim = Dimension(Point(xmaxdim2, GridsY[0], 0), Point( + xmaxdim2, Ymax, 0), DT2_5_mm) + gs.dimensions.append(dim) + + gridsY = [] + count = 0 + for i in GridsY: + gridsY.append(Grid.by_startpoint_endpoint( + Line(Point(-GridEx, i, 0), Point(Xmax+GridEx, i, 0)), GridsYLable[count])) + try: + dim = Dimension(Point(xmaxdim1, i, 0), Point( + xmaxdim1, GridsY[count+1], 0)) # ,DT3_5_mm) + gs.dimensions.append(dim) + except: + pass + count = count + 1 + gs.gridsX = gridsX + gs.gridsY = gridsY + return gs + + def write(self, project): + for x in self.gridsX: + project.objects.append(x) + for i in x.grid_heads: + i.write(project) + for y in self.gridsY: + project.objects.append(y) + for j in y.grid_heads: + j.write(project) + for z in self.dimensions: + z.write(project) + return self + + +class Panel: + # Panel + def __init__(self): + self.id = generateID() + self.type = __class__.__name__ + self.extrusion = None + self.thickness = 0 + self.name = None + self.perimeter: float = 0 + self.coordinatesystem: CoordinateSystem = CSGlobal + self.colorint = None + self.colorlst = [] + self.origincurve = None + + def serialize(self): + id_value = str(self.id) if not isinstance( + self.id, (str, int, float)) else self.id + return { + 'id': id_value, + 'type': self.type, + 'extrusion': self.extrusion, + 'thickness': self.thickness, + 'name': self.name, + 'perimeter': self.perimeter, + 'coordinatesystem': self.coordinatesystem.serialize(), + 'color': self.color, + 'colorlst': self.colorlst, + 'origincurve': self.origincurve + } + + @staticmethod + def deserialize(data): + panel = Panel() + panel.id = data.get('id') + panel.type = data.get('type') + panel.extrusion = data.get('extrusion') + panel.thickness = data.get('thickness', 0) + panel.name = data.get('name', "none") + panel.perimeter = data.get('perimeter', 0) + panel.coordinatesystem = CoordinateSystem.deserialize( + data['coordinatesystem']) + panel.color = data.get('color') + panel.colorlst = data.get('colorlst', []) + panel.origincurve = data.get('origincurve') + + return panel + + @classmethod + def by_polycurve_thickness(self, polycurve: PolyCurve, thickness: float, offset: float, name: str, colorrgbint): + # Create panel by polycurve + p1 = Panel() + p1.name = name + p1.thickness = thickness + p1.extrusion = Extrusion.by_polycurve_height( + polycurve, thickness, offset) + p1.origincurve = polycurve + p1.colorint = colorrgbint + for j in range(int(len(p1.extrusion.verts) / 3)): + p1.colorlst.append(colorrgbint) + return p1 + + @classmethod + def by_baseline_height(self, baseline: Line, height: float, thickness: float, name: str, colorrgbint): + # place panel vertical from baseline + p1 = Panel() + p1.name = name + p1.thickness = thickness + polycurve = PolyCurve.by_points( + [baseline.start, + baseline.end, + Point.translate(baseline.end, Vector3(0, 0, height)), + Point.translate(baseline.start, Vector3(0, 0, height))]) + p1.extrusion = Extrusion.by_polycurve_height(polycurve, thickness, 0) + p1.origincurve = polycurve + for j in range(int(len(p1.extrusion.verts) / 3)): + p1.colorlst.append(colorrgbint) + return p1 + +class Wall: + def __init__(self): + self.id = generateID() + self.type = __class__.__name__ + self.name = None + self.verts = None + self.faces = None + # self.polycurve = None or self.profile + self.parms = None + self.coordinatesystem: CoordinateSystem = CSGlobal + self.colorlst = None + + @classmethod + def by_mesh(self, verts=list, faces=list): + wall = Wall() + wall.verts = [vertex * project.scale for vertex in verts] + wall.faces = list(faces) + return wall + + def __str__(self) -> str: + return f"{self.type}(Name={self.name})" + +class Room: + def __init__(self): + self.id = generateID() + self.type = __class__.__name__ + self.name = None + self.extrusion = None + self.verts = None + self.faces = None + self.topsurface = None + self.bottomsurface = None + self.parms = None + self.coordinatesystem: CoordinateSystem = CSGlobal + self.colorlst = None + +sqrt2 = math.sqrt(2) + +# Hierachie: +# point 2D +# line 2D +# PolyCurve2D 2D +# shape is een parametrische vorm heeft als resultaat een 2D curve +# section is een profiel met eigenschappen HEA200, 200,200,10,10,5 en eventuele rekenkundige eigenschappen. +# beam is een object wat in 3D zit met materiaal enz. + + +class CChannelParallelFlange: + def __init__(self, name, h, b, tw, tf, r, ex): + self.Description = "C-channel with parallel flange" + self.ID = "C_PF" + + # parameters + self.id = generateID() + self.type = __class__.__name__ + self.name = name + self.curve = [] + self.h = h # height + self.b = b # width + self.tw = tw # web thickness + self.tf = tf # flange thickness + self.r1 = r # web fillet + self.ex = ex # centroid horizontal + self.IfcProfileDef = "IfcUShapeProfileDef" + + # describe points + p1 = Point2D(-ex, -h / 2) # left bottom + p2 = Point2D(b - ex, -h / 2) # right bottom + p3 = Point2D(b - ex, -h / 2 + tf) + p4 = Point2D(-ex + tw + r, -h / 2 + tf) # start arc + p5 = Point2D(-ex + tw + r, -h / 2 + tf + r) # second point arc + p6 = Point2D(-ex + tw, -h / 2 + tf + r) # end arc + p7 = Point2D(-ex + tw, h / 2 - tf - r) # start arc + p8 = Point2D(-ex + tw + r, h / 2 - tf - r) # second point arc + p9 = Point2D(-ex + tw + r, h / 2 - tf) # end arc + p10 = Point2D(b - ex, h / 2 - tf) + p11 = Point2D(b - ex, h / 2) # right top + p12 = Point2D(-ex, h / 2) # left top + + # describe curves + l1 = Line2D(p1, p2) + l2 = Line2D(p2, p3) + l3 = Line2D(p3, p4) + l4 = Arc2D(p4, p5, p6) + l5 = Line2D(p6, p7) + l6 = Arc2D(p7, p8, p9) + l7 = Line2D(p9, p10) + l8 = Line2D(p10, p11) + l9 = Line2D(p11, p12) + l10 = Line2D(p12, p1) + + self.curve = PolyCurve2D().by_joined_curves( + [l1, l2, l3, l4, l5, l6, l7, l8, l9, l10]) + + def serialize(self): + return { + 'id': self.id, + 'type': self.type, + 'Description': self.Description, + 'ID': self.ID, + 'name': self.name, + 'h': self.h, + 'b': self.b, + 'tw': self.tw, + 'tf': self.tf, + 'r1': self.r1, + 'ex': self.ex, + 'curve': self.curve.serialize() if self.curve else None + } + + @staticmethod + def deserialize(data): + c_channel = CChannelParallelFlange( + name=data.get('name'), + h=data.get('h'), + b=data.get('b'), + tw=data.get('tw'), + tf=data.get('tf'), + r=data.get('r1'), + ex=data.get('ex') + ) + + c_channel.Description = data.get( + 'Description', "C-channel with parallel flange") + c_channel.ID = data.get('ID', "C_PF") + c_channel.curve = PolyCurve2D.deserialize( + data['curve']) if 'curve' in data else None + + return c_channel + + def __str__(self): + return f"{self.type} ({self.name})" + + +class CChannelSlopedFlange: + def __init__(self, name, h, b, tw, tf, r1, r2, tl, sa, ex): + self.Description = "C-channel with sloped flange" + self.ID = "C_SF" + + # parameters + self.id = generateID() + self.type = __class__.__name__ + self.name = name + self.curve = [] + self.b = b # width + self.h = h # height + self.tf = tf # flange thickness + self.tw = tw # web thickness + self.r1 = r1 # web fillet + self.r11 = r1 / sqrt2 + self.r2 = r2 # flange fillet + self.r21 = r2 / sqrt2 + self.tl = tl # flange thickness location from right + self.sa = math.radians(sa) # the angle of sloped flange in degrees + self.ex = ex # centroid horizontal + self.IfcProfileDef = "IfcUShapeProfileDef" + + # describe points + p1 = Point2D(-ex, -h / 2) # left bottom + p2 = Point2D(b - ex, -h / 2) # right bottom + p3 = Point2D(b - ex, -h / 2 + tf - math.tan(self.sa) + * tl - r2) # start arc + p4 = Point2D(b - ex - r2 + self.r21, -h / 2 + tf - + math.tan(self.sa) * tl - r2 + self.r21) # second point arc + p5 = Point2D(b - ex - r2 + math.sin(self.sa) * r2, -h / + 2 + tf - math.tan(self.sa) * (tl - r2)) # end arc + p6 = Point2D(-ex + tw + r1 - math.sin(self.sa) * r1, -h / 2 + + tf + math.tan(self.sa) * (b - tl - tw - r1)) # start arc + p7 = Point2D(-ex + tw + r1 - self.r11, -h / 2 + tf + math.tan(self.sa) + * (b - tl - tw - r1) + r1 - self.r11) # second point arc + p8 = Point2D(-ex + tw, -h / 2 + tf + math.tan(self.sa) + * (b - tl - tw) + r1) # end arc + p9 = Point2D(p8.x, -p8.y) # start arc + p10 = Point2D(p7.x, -p7.y) # second point arc + p11 = Point2D(p6.x, -p6.y) # end arc + p12 = Point2D(p5.x, -p5.y) # start arc + p13 = Point2D(p4.x, -p4.y) # second point arc + p14 = Point2D(p3.x, -p3.y) # end arc + p15 = Point2D(p2.x, -p2.y) # right top + p16 = Point2D(p1.x, -p1.y) # left top + + # describe curves + l1 = Line2D(p1, p2) + l2 = Line2D(p2, p3) + l3 = Arc2D(p3, p4, p5) + l4 = Line2D(p5, p6) + l5 = Arc2D(p6, p7, p8) + l6 = Line2D(p8, p9) + l7 = Arc2D(p9, p10, p11) + l8 = Line2D(p11, p12) + l9 = Arc2D(p12, p13, p14) + l10 = Line2D(p14, p15) + l11 = Line2D(p15, p16) + l12 = Line2D(p16, p1) + + self.curve = PolyCurve2D().by_joined_curves( + [l1, l2, l3, l4, l5, l6, l7, l8, l9, l10, l11, l12]) + + def serialize(self): + return { + 'Description': self.Description, + 'ID': self.ID, + 'id': self.id, + 'type': self.type, + 'name': self.name, + 'h': self.h, + 'b': self.b, + 'tw': self.tw, + 'tf': self.tf, + 'r1': self.r1, + 'r11': self.r11, + 'r2': self.r2, + 'r21': self.r21, + 'tl': self.tl, + 'sa': self.sa, + 'ex': self.ex, + 'curve': self.curve.serialize() if self.curve else None + } + + @staticmethod + def deserialize(data): + c_channel_sf = CChannelSlopedFlange( + name=data.get('name'), + h=data.get('h'), + b=data.get('b'), + tw=data.get('tw'), + tf=data.get('tf'), + r1=data.get('r1'), + r2=data.get('r2'), + tl=data.get('tl'), + sa=data.get('sa'), + ex=data.get('ex') + ) + + c_channel_sf.Description = data.get( + 'Description', "C-channel with sloped flange") + c_channel_sf.ID = data.get('ID', "C_SF") + c_channel_sf.id = data.get('id') + c_channel_sf.type = data.get('type') + c_channel_sf.curve = PolyCurve2D.deserialize( + data['curve']) if 'curve' in data else None + + return c_channel_sf + + def __str__(self): + return f"{self.type} ({self.name})" + + +class IShapeParallelFlange: + def __init__(self, name, h, b, tw, tf, r): + self.Description = "I Shape profile with parallel flange" + self.ID = "I_PF" + # HEA, IPE, HEB, HEM etc. + + # parameters + self.id = generateID() + self.type = __class__.__name__ + self.name = name + self.h = h # height + self.b = b # width + self.tw = tw # web thickness + self.tf = tf # flange thickness + self.r = r # web fillet + self.r1 = r1 = r / sqrt2 + self.IfcProfileDef = "IfcIShapeProfileDef" + + # describe points + p1 = Point2D(b / 2, -h / 2) # right bottom + p2 = Point2D(b / 2, -h / 2 + tf) + p3 = Point2D(tw / 2 + r, -h / 2 + tf) # start arc + # second point arc + p4 = Point2D(tw / 2 + r - r1, (-h / 2 + tf + r - r1)) + p5 = Point2D(tw / 2, -h / 2 + tf + r) # end arc + p6 = Point2D(tw / 2, h / 2 - tf - r) # start arc + p7 = Point2D(tw / 2 + r - r1, h / 2 - tf - r + r1) # second point arc + p8 = Point2D(tw / 2 + r, h / 2 - tf) # end arc + p9 = Point2D(b / 2, h / 2 - tf) + p10 = Point2D((b / 2), (h / 2)) # right top + p11 = Point2D(-p10.x, p10.y) # left top + p12 = Point2D(-p9.x, p9.y) + p13 = Point2D(-p8.x, p8.y) # start arc + p14 = Point2D(-p7.x, p7.y) # second point arc + p15 = Point2D(-p6.x, p6.y) # end arc + p16 = Point2D(-p5.x, p5.y) # start arc + p17 = Point2D(-p4.x, p4.y) # second point arc + p18 = Point2D(-p3.x, p3.y) # end arc + p19 = Point2D(-p2.x, p2.y) + p20 = Point2D(-p1.x, p1.y) + + # describe curves + l1 = Line2D(p1, p2) + l2 = Line2D(p2, p3) + l3 = Arc2D(p3, p4, p5) + l4 = Line2D(p5, p6) + l5 = Arc2D(p6, p7, p8) + l6 = Line2D(p8, p9) + l7 = Line2D(p9, p10) + l8 = Line2D(p10, p11) + l9 = Line2D(p11, p12) + l10 = Line2D(p12, p13) + l11 = Arc2D(p13, p14, p15) + l12 = Line2D(p15, p16) + l13 = Arc2D(p16, p17, p18) + l14 = Line2D(p18, p19) + l15 = Line2D(p19, p20) + l16 = Line2D(p20, p1) + + self.curve = PolyCurve2D().by_joined_curves( + [l1, l2, l3, l4, l5, l6, l7, l8, l9, l10, l11, l12, l13, l14, l15, l16]) + + def serialize(self): + return { + 'Description': self.Description, + 'ID': self.ID, + 'id': self.id, + 'type': self.type, + 'name': self.name, + 'h': self.h, + 'b': self.b, + 'tw': self.tw, + 'tf': self.tf, + 'r': self.r, + 'r1': self.r1, + 'curve': self.curve.serialize() if self.curve else None + } + + @staticmethod + def deserialize(data): + i_shape_pf = IShapeParallelFlange( + name=data.get('name'), + h=data.get('h'), + b=data.get('b'), + tw=data.get('tw'), + tf=data.get('tf'), + r=data.get('r') + ) + + i_shape_pf.Description = data.get( + 'Description', "I Shape profile with parallel flange") + i_shape_pf.ID = data.get('ID', "I_PF") + i_shape_pf.id = data.get('id') + i_shape_pf.type = data.get('type') + i_shape_pf.r1 = data.get('r1') + i_shape_pf.curve = PolyCurve2D.deserialize( + data['curve']) if 'curve' in data else None + + return i_shape_pf + + def __str__(self): + return f"{self.type} ({self.name})" + + +class Rectangle: + def __init__(self, name, b, h): + self.Description = "Rectangle" + self.ID = "Rec" + + # parameters + self.id = generateID() + self.type = __class__.__name__ + self.name = name + self.curve = [] + self.h = h # height + self.b = b # width + self.IfcProfileDef = "IfcRectangleProfileDef" + + # describe points + p1 = Point2D(b / 2, -h / 2) # right bottom + p2 = Point2D(b / 2, h / 2) # right top + p3 = Point2D(-b / 2, h / 2) # left top + p4 = Point2D(-b / 2, -h / 2) # left bottom + + # describe curves + l1 = Line2D(p1, p2) + l2 = Line2D(p2, p3) + l3 = Line2D(p3, p4) + l4 = Line2D(p4, p1) + + self.curve = PolyCurve2D().by_joined_curves([l1, l2, l3, l4]) + + def serialize(self): + return { + 'Description': self.Description, + 'ID': self.ID, + 'id': self.id, + 'type': self.type, + 'name': self.name, + 'h': self.h, + 'b': self.b, + 'curve': self.curve.serialize() if self.curve else None + } + + @staticmethod + def deserialize(data): + rectangle = Rectangle( + name=data.get('name'), + b=data.get('b'), + h=data.get('h') + ) + + rectangle.Description = data.get('Description', "Rectangle") + rectangle.ID = data.get('ID', "Rec") + rectangle.id = data.get('id') + rectangle.type = data.get('type') + rectangle.curve = PolyCurve2D.deserialize( + data['curve']) if 'curve' in data else None + + return rectangle + + def __str__(self): + return f"{self.type} ({self.name})" + + +class Round: + def __init__(self, name, r): + self.Description = "Round" + self.ID = "Rnd" + + # parameters + self.id = generateID() + self.type = __class__.__name__ + self.name = name + self.curve = [] + self.r = r # radius + self.data = (name, r, "Round") + dr = r / sqrt2 # grootste deel + self.IfcProfileDef = "IfcCircleProfileDef" + + # describe points + p1 = Point2D(r, 0) # right middle + p2 = Point2D(dr, dr) + p3 = Point2D(0, r) # middle top + p4 = Point2D(-dr, dr) + p5 = Point2D(-r, 0) # left middle + p6 = Point2D(-dr, -dr) + p7 = Point2D(0, -r) # middle bottom + p8 = Point2D(dr, -dr) + + # describe curves + l1 = Arc2D(p1, p2, p3) + l2 = Arc2D(p3, p4, p5) + l3 = Arc2D(p5, p6, p7) + l4 = Arc2D(p7, p8, p1) + + self.curve = PolyCurve2D().by_joined_curves([l1, l2, l3, l4]) + + def serialize(self): + return { + 'Description': self.Description, + 'ID': self.ID, + 'id': self.id, + 'type': self.type, + 'name': self.name, + 'r': self.r, + 'data': self.data, + 'curve': self.curve.serialize() if self.curve else None + } + + @staticmethod + def deserialize(data): + round_shape = Round( + name=data.get('name'), + r=data.get('r') + ) + + round_shape.Description = data.get('Description', "Round") + round_shape.ID = data.get('ID', "Rnd") + round_shape.id = data.get('id') + round_shape.type = data.get('type') + round_shape.data = data.get( + 'data', (data.get('name'), data.get('r'), "Round")) + round_shape.curve = PolyCurve2D.deserialize( + data['curve']) if 'curve' in data else None + + return round_shape + + def __str__(self): + return f"{self.type} ({self.name})" + + +class Roundtube: + def __init__(self, name, d, t): + self.Description = "Round Tube Profile" + self.ID = "Tube" + + # parameters + self.id = generateID() + self.type = __class__.__name__ + self.name = name + self.curve = [] + self.d = d + self.r = d/2 # radius + self.t = t # wall thickness + self.data = (name, d, t, "Round Tube Profile") + dr = self.r / sqrt2 # grootste deel + r = self.r + ri = r-t + dri = ri / sqrt2 + self.IfcProfileDef = "IfcCircleHollowProfileDef" + + # describe points + p1 = Point2D(r, 0) # right middle + p2 = Point2D(dr, dr) + p3 = Point2D(0, r) # middle top + p4 = Point2D(-dr, dr) + p5 = Point2D(-r, 0) # left middle + p6 = Point2D(-dr, -dr) + p7 = Point2D(0, -r) # middle bottom + p8 = Point2D(dr, -dr) + + p9 = Point2D(ri, 0) # right middle inner + p10 = Point2D(dri, dri) + p11 = Point2D(0, ri) # middle top inner + p12 = Point2D(-dri, dri) + p13 = Point2D(-ri, 0) # left middle inner + p14 = Point2D(-dri, -dri) + p15 = Point2D(0, -ri) # middle bottom inner + p16 = Point2D(dri, -dri) + + # describe curves + l1 = Arc2D(p1, p2, p3) + l2 = Arc2D(p3, p4, p5) + l3 = Arc2D(p5, p6, p7) + l4 = Arc2D(p7, p8, p1) + + l5 = Line2D(p1, p9) + + l6 = Arc2D(p9, p10, p11) + l7 = Arc2D(p11, p12, p13) + l8 = Arc2D(p13, p14, p15) + l9 = Arc2D(p15, p16, p9) + l10 = Line2D(p9, p1) + + self.curve = PolyCurve2D().by_joined_curves( + [l1, l2, l3, l4, l5, l6, l7, l8, l9, l10]) + + def serialize(self): + return { + 'Description': self.Description, + 'ID': self.ID, + 'id': self.id, + 'type': self.type, + 'name': self.name, + 'd': self.d, + 'r': self.r, + 't': self.t, + 'data': self.data, + 'curve': self.curve.serialize() if self.curve else None + } + + @staticmethod + def deserialize(data): + roundtube = Roundtube( + name=data.get('name'), + d=data.get('d'), + t=data.get('t') + ) + + roundtube.Description = data.get('Description', "Round Tube Profile") + roundtube.ID = data.get('ID', "Tube") + roundtube.id = data.get('id') + roundtube.type = data.get('type') + roundtube.data = data.get('data', (data.get('name'), data.get( + 'd'), data.get('t'), "Round Tube Profile")) + roundtube.curve = PolyCurve2D.deserialize( + data['curve']) if 'curve' in data else None + + return roundtube + + def __str__(self): + return f"{self.type} ({self.name})" + + +class LAngle: + def __init__(self, name, h, b, tw, tf, r1, r2, ex, ey): + self.Description = "LAngle" + self.ID = "L" + + # parameters + self.id = generateID() + self.type = __class__.__name__ + self.name = name + self.curve = [] + self.b = b # width + self.h = h # height + self.tw = tw # wall nominal thickness + self.tf = tw + self.r1 = r1 # inner fillet + self.r11 = r1 / sqrt2 + self.r2 = r2 # outer fillet + self.r21 = r2 / sqrt2 + self.ex = ex # from left + self.ey = ey # from bottom + self.IfcProfileDef = "IfcLShapeProfileDef" + + # describe points + p1 = Point2D(-ex, -ey) # left bottom + p2 = Point2D(b - ex, -ey) # right bottom + p3 = Point2D(b - ex, -ey + tf - r2) # start arc + p4 = Point2D(b - ex - r2 + self.r21, -ey + tf - + r2 + self.r21) # second point arc + p5 = Point2D(b - ex - r2, -ey + tf) # end arc + p6 = Point2D(-ex + tf + r1, -ey + tf) # start arc + p7 = Point2D(-ex + tf + r1 - self.r11, -ey + tf + + r1 - self.r11) # second point arc + p8 = Point2D(-ex + tf, -ey + tf + r1) # end arc + p9 = Point2D(-ex + tf, h - ey - r2) # start arc + p10 = Point2D(-ex + tf - r2 + self.r21, h - ey - + r2 + self.r21) # second point arc + p11 = Point2D(-ex + tf - r2, h - ey) # end arc + p12 = Point2D(-ex, h - ey) # left top + + # describe curves + l1 = Line2D(p1, p2) + l2 = Line2D(p2, p3) + l3 = Arc2D(p3, p4, p5) + l4 = Line2D(p5, p6) + l5 = Arc2D(p6, p7, p8) + l6 = Line2D(p8, p9) + l7 = Arc2D(p9, p10, p11) + l8 = Line2D(p11, p12) + l9 = Line2D(p12, p1) + + self.curve = PolyCurve2D().by_joined_curves( + [l1, l2, l3, l4, l5, l6, l7, l8, l9]) + + def serialize(self): + return { + 'Description': self.Description, + 'ID': self.ID, + 'id': self.id, + 'type': self.type, + 'name': self.name, + 'b': self.b, + 'h': self.h, + 'tw': self.tw, + 'tf': self.tf, + 'r1': self.r1, + 'r11': self.r11, + 'r2': self.r2, + 'r21': self.r21, + 'ex': self.ex, + 'ey': self.ey, + 'curve': self.curve.serialize() if self.curve else None + } + + @staticmethod + def deserialize(data): + langle = LAngle( + name=data.get('name'), + h=data.get('h'), + b=data.get('b'), + tw=data.get('tw'), + tf=data.get('tf'), + r1=data.get('r1'), + r2=data.get('r2'), + ex=data.get('ex'), + ey=data.get('ey') + ) + + langle.Description = data.get('Description', "LAngle") + langle.ID = data.get('ID', "L") + langle.id = data.get('id') + langle.type = data.get('type') + langle.r11 = data.get('r11') + langle.r21 = data.get('r21') + langle.curve = PolyCurve2D.deserialize( + data['curve']) if 'curve' in data else None + + return langle + + def __str__(self): + return f"{self.type} ({self.name})" + + +class TProfile: + # ToDo: inner outer fillets in polycurve + def __init__(self, name, h, b, tw, tf, r, r1, r2, ex, ey): + self.Description = "TProfile" + self.ID = "T" + + # parameters + self.id = generateID() + self.type = __class__.__name__ + self.name = name + self.curve = [] + self.b = b # width + self.h = h # height + self.tw = tw # wall nominal thickness + self.tf = tw + self.r = r # inner fillet + self.r01 = r/sqrt2 + self.r1 = r1 # outer fillet flange + self.r11 = r1 / sqrt2 + self.r2 = r2 # outer fillet top web + self.r21 = r2 / sqrt2 + self.ex = ex # from left + self.ey = ey # from bottom + self.IfcProfileDef = "IfcTShapeProfileDef" + + # describe points + p1 = Point2D(-ex, -ey) # left bottom + p2 = Point2D(b - ex, -ey) # right bottom + p3 = Point2D(b - ex, -ey + tf - r1) # start arc + p4 = Point2D(b - ex - r1 + self.r11, -ey + tf - + r1 + self.r11) # second point arc + p5 = Point2D(b - ex - r1, -ey + tf) # end arc + p6 = Point2D(0.5 * tw + r, -ey + tf) # start arc + p7 = Point2D(0.5 * tw + r - self.r01, -ey + tf + + r - self.r01) # second point arc + p8 = Point2D(0.5 * tw, -ey + tf + r) # end arc + p9 = Point2D(0.5 * tw, -ey + h - r2) # start arc + p10 = Point2D(0.5 * tw - self.r21, -ey + h - + r2 + self.r21) # second point arc + p11 = Point2D(0.5 * tw - r2, -ey + h) # end arc + + p12 = Point2D(-p11.x, p11.y) + p13 = Point2D(-p10.x, p10.y) + p14 = Point2D(-p9.x, p9.y) + p15 = Point2D(-p8.x, p8.y) + p16 = Point2D(-p7.x, p7.y) + p17 = Point2D(-p6.x, p6.y) + p18 = Point2D(-p5.x, p5.y) + p19 = Point2D(-p4.x, p4.y) + p20 = Point2D(-p3.x, p3.y) + + # describe curves + l1 = Line2D(p1, p2) + + l2 = Line2D(p2, p3) + l3 = Arc2D(p3, p4, p5) + l4 = Line2D(p5, p6) + l5 = Arc2D(p6, p7, p8) + l6 = Line2D(p8, p9) + l7 = Arc2D(p9, p10, p11) + l8 = Line2D(p11, p12) + + l9 = Arc2D(p12, p13, p14) + l10 = Line2D(p14, p15) + l11 = Arc2D(p15, p16, p17) + l12 = Line2D(p17, p18) + l13 = Arc2D(p18, p19, p20) + l14 = Line2D(p20, p1) + + self.curve = PolyCurve2D().by_joined_curves( + [l1, l2, l3, l4, l5, l6, l7, l8, l9, l10, l11, l12, l13, l14]) + + def serialize(self): + return { + 'Description': self.Description, + 'ID': self.ID, + 'id': self.id, + 'type': self.type, + 'name': self.name, + 'b': self.b, + 'h': self.h, + 'tw': self.tw, + 'tf': self.tf, + 'r': self.r, + 'r01': self.r01, + 'r1': self.r1, + 'r11': self.r11, + 'r2': self.r2, + 'r21': self.r21, + 'ex': self.ex, + 'ey': self.ey, + 'curve': self.curve.serialize() if self.curve else None + } + + @staticmethod + def deserialize(data): + t_profile = TProfile( + name=data.get('name'), + h=data.get('h'), + b=data.get('b'), + tw=data.get('tw'), + tf=data.get('tf'), + r=data.get('r'), + r1=data.get('r1'), + r2=data.get('r2'), + ex=data.get('ex'), + ey=data.get('ey') + ) + + t_profile.Description = data.get('Description', "TProfile") + t_profile.ID = data.get('ID', "T") + t_profile.id = data.get('id') + t_profile.type = data.get('type') + t_profile.r01 = data.get('r01') + t_profile.r11 = data.get('r11') + t_profile.r21 = data.get('r21') + t_profile.curve = PolyCurve2D.deserialize( + data['curve']) if 'curve' in data else None + + return t_profile + + def __str__(self): + return f"{self.type} ({self.name})" + + +class RectangleHollowSection: + def __init__(self, name, h, b, t, r1, r2): + self.Description = "Rectangle Hollow Section" + self.ID = "RHS" + + # parameters + self.id = generateID() + self.type = __class__.__name__ + self.name = name + self.curve = [] + self.h = h # height + self.b = b # width + self.t = t # thickness + self.r1 = r1 # outer radius + self.r2 = r2 # inner radius + dr = r1 - r1 / sqrt2 + dri = r2 - r2 / sqrt2 + bi = b-t + hi = h-t + self.IfcProfileDef = "IfcRectangleHollowProfileDef" + + # describe points + p1 = Point2D(-b / 2 + r1, - h / 2) # left bottom end arc + p2 = Point2D(b / 2 - r1, - h / 2) # right bottom start arc + p3 = Point2D(b / 2 - dr, - h / 2 + dr) # right bottom mid arc + p4 = Point2D(b / 2, - h / 2 + r1) # right bottom end arc + p5 = Point2D(p4.x, -p4.y) # right start arc + p6 = Point2D(p3.x, -p3.y) # right mid arc + p7 = Point2D(p2.x, -p2.y) # right end arc + p8 = Point2D(-p7.x, p7.y) # left start arc + p9 = Point2D(-p6.x, p6.y) # left mid arc + p10 = Point2D(-p5.x, p5.y) # left end arc + p11 = Point2D(p10.x, -p10.y) # right bottom start arc + p12 = Point2D(p9.x, -p9.y) # right bottom mid arc + + # inner part + p13 = Point2D(-bi / 2 + r2, - hi / 2) # left bottom end arc + p14 = Point2D(bi / 2 - r2, - hi / 2) # right bottom start arc + p15 = Point2D(bi / 2 - dri, - hi / 2 + dri) # right bottom mid arc + p16 = Point2D(bi / 2, - hi / 2 + r2) # right bottom end arc + p17 = Point2D(p16.x, -p16.y) # right start arc + p18 = Point2D(p15.x, -p15.y) # right mid arc + p19 = Point2D(p14.x, -p14.y) # right end arc + p20 = Point2D(-p19.x, p19.y) # left start arc + p21 = Point2D(-p18.x, p18.y) # left mid arc + p22 = Point2D(-p17.x, p17.y) # left end arc + p23 = Point2D(p22.x, -p22.y) # right bottom start arc + p24 = Point2D(p21.x, -p21.y) # right bottom mid arc + + # describe outer curves + l1 = Line2D(p1, p2) + l2 = Arc2D(p2, p3, p4) + l3 = Line2D(p4, p5) + l4 = Arc2D(p5, p6, p7) + l5 = Line2D(p7, p8) + l6 = Arc2D(p8, p9, p10) + l7 = Line2D(p10, p11) + l8 = Arc2D(p11, p12, p1) + + l9 = Line2D(p1, p13) + # describe inner curves + l10 = Line2D(p13, p14) + l11 = Arc2D(p14, p15, p16) + l12 = Line2D(p16, p17) + l13 = Arc2D(p17, p18, p19) + l14 = Line2D(p19, p20) + l15 = Arc2D(p20, p21, p22) + l16 = Line2D(p22, p23) + l17 = Arc2D(p23, p24, p13) + + l18 = Line2D(p13, p1) + + self.curve = PolyCurve2D().by_joined_curves( + [l1, l2, l3, l4, l5, l6, l7, l8, l9, l10, l11, l12, l13, l14, l15, l16, l17, l18]) + + def serialize(self): + return { + 'Description': self.Description, + 'ID': self.ID, + 'id': self.id, + 'type': self.type, + 'name': self.name, + 'h': self.h, + 'b': self.b, + 't': self.t, + 'r1': self.r1, + 'r2': self.r2, + 'curve': self.curve.serialize() if self.curve else None + } + + @staticmethod + def deserialize(data): + rhs = RectangleHollowSection( + name=data.get('name'), + h=data.get('h'), + b=data.get('b'), + t=data.get('t'), + r1=data.get('r1'), + r2=data.get('r2') + ) + + rhs.Description = data.get('Description', "Rectangle Hollow Section") + rhs.ID = data.get('ID', "RHS") + rhs.id = data.get('id') + rhs.type = data.get('type') + rhs.curve = PolyCurve2D.deserialize( + data['curve']) if 'curve' in data else None + + return rhs + + def __str__(self): + return f"{self.type} ({self.name})" + + +class CProfile: + def __init__(self, name, b, h, t, r1, ex): + self.Description = "Cold Formed C Profile" + self.ID = "CP" + + # parameters + self.id = generateID() + self.type = __class__.__name__ + self.name = name + self.curve = [] + self.h = h # height + self.b = b # width + self.t = t # flange thickness + self.r1 = r1 # outer radius + self.r2 = r1-t # inner radius + r2 = r1-t + + self.ex = ex + self.ey = h/2 + dr = r1 - r1/sqrt2 + dri = r2 - r2/sqrt2 + hi = h-t + self.IfcProfileDef = "Unknown" + + # describe points + p1 = Point2D(b-ex, -h/2) # right bottom + p2 = Point2D(r1-ex, -h/2) + p3 = Point2D(dr-ex, -h/2+dr) + p4 = Point2D(0-ex, -h/2+r1) + p5 = Point2D(p4.x, -p4.y) + p6 = Point2D(p3.x, -p3.y) + p7 = Point2D(p2.x, -p2.y) + p8 = Point2D(p1.x, -p1.y) # right top + p9 = Point2D(b-ex, hi/2) # right top inner + p10 = Point2D(t+r2-ex, hi/2) + p11 = Point2D(t+dri-ex, hi/2-dri) + p12 = Point2D(t-ex, hi/2-r2) + p13 = Point2D(p12.x, -p12.y) + p14 = Point2D(p11.x, -p11.y) + p15 = Point2D(p10.x, -p10.y) + p16 = Point2D(p9.x, -p9.y) # right bottom inner + # describe outer curves + l1 = Line2D(p1, p2) # bottom + l2 = Arc2D(p2, p3, p4) # right outer fillet + l3 = Line2D(p4, p5) # left outer web + l4 = Arc2D(p5, p6, p7) # left top outer fillet + l5 = Line2D(p7, p8) # outer top + l6 = Line2D(p8, p9) + l7 = Line2D(p9, p10) + l8 = Arc2D(p10, p11, p12) # left top inner fillet + l9 = Line2D(p12, p13) + l10 = Arc2D(p13, p14, p15) # left botom inner fillet + l11 = Line2D(p15, p16) + l12 = Line2D(p16, p1) + + self.curve = PolyCurve2D().by_joined_curves( + [l1, l2, l3, l4, l5, l6, l7, l8, l9, l10, l11, l12]) + + def serialize(self): + return { + 'Description': self.Description, + 'ID': self.ID, + 'id': self.id, + 'type': self.type, + 'name': self.name, + 'h': self.h, + 'b': self.b, + 't': self.t, + 'r1': self.r1, + 'r2': self.r2, + 'ex': self.ex, + 'ey': self.ey, + 'curve': self.curve.serialize() if self.curve else None + } + + @staticmethod + def deserialize(data): + c_profile = CProfile( + name=data.get('name'), + b=data.get('b'), + h=data.get('h'), + t=data.get('t'), + r1=data.get('r1'), + ex=data.get('ex') + ) + + c_profile.Description = data.get( + 'Description', "Cold Formed C Profile") + c_profile.ID = data.get('ID', "CP") + c_profile.id = data.get('id') + c_profile.type = data.get('type') + c_profile.r2 = data.get('r2') + c_profile.ey = data.get('ey') + c_profile.curve = PolyCurve2D.deserialize( + data['curve']) if 'curve' in data else None + + return c_profile + + def __str__(self): + return f"{self.type} ({self.name})" + + +class CProfileWithLips: + def __init__(self, name, b, h, h1, t, r1, ex): + self.Description = "Cold Formed C Profile with Lips" + self.ID = "CPWL" + + # parameters + self.id = generateID() + self.type = __class__.__name__ + self.name = name + self.curve = [] + self.h = h # height + self.b = b # width + self.h1 = h1 # lip length + self.t = t # flange thickness + self.r1 = r1 # outer radius + self.r2 = r1-t # inner radius + r2 = r1-t + + self.ex = ex + self.ey = h/2 + dr = r1 - r1/sqrt2 + dri = r2 - r2/sqrt2 + hi = h-t + self.IfcProfileDef = "Unknown" + + # describe points + p1 = Point2D(b-ex-r1, -h/2) # right bottom before fillet + p2 = Point2D(r1-ex, -h/2) + p3 = Point2D(dr-ex, -h/2+dr) + p4 = Point2D(0-ex, -h/2+r1) + p5 = Point2D(p4.x, -p4.y) + p6 = Point2D(p3.x, -p3.y) + p7 = Point2D(p2.x, -p2.y) + p8 = Point2D(p1.x, -p1.y) # right top before fillet + p9 = Point2D(b-ex-dr, h/2-dr) # middle point arc + p10 = Point2D(b-ex, h/2-r1) # end fillet + p11 = Point2D(b-ex, h/2-h1) + p12 = Point2D(b-ex-t, h/2-h1) # bottom lip + p13 = Point2D(b-ex-t, h/2-t-r2) # start inner fillet right top + p14 = Point2D(b-ex-t-dri, h/2-t-dri) + p15 = Point2D(b-ex-t-r2, h/2-t) # end inner fillet right top + p16 = Point2D(0-ex+t+r2, h/2-t) + p17 = Point2D(0-ex+t+dri, h/2-t-dri) + p18 = Point2D(0-ex+t, h/2-t-r2) + + p19 = Point2D(p18.x, -p18.y) + p20 = Point2D(p17.x, -p17.y) + p21 = Point2D(p16.x, -p16.y) + p22 = Point2D(p15.x, -p15.y) + p23 = Point2D(p14.x, -p14.y) + p24 = Point2D(p13.x, -p13.y) + p25 = Point2D(p12.x, -p12.y) + p26 = Point2D(p11.x, -p11.y) + p27 = Point2D(p10.x, -p10.y) + p28 = Point2D(p9.x, -p9.y) + + # describe outer curves + l1 = Line2D(p1, p2) + l2 = Arc2D(p2, p3, p4) + l3 = Line2D(p4, p5) + l4 = Arc2D(p5, p6, p7) # outer fillet right top + l5 = Line2D(p7, p8) + l6 = Arc2D(p8, p9, p10) + l7 = Line2D(p10, p11) + l8 = Line2D(p11, p12) + l9 = Line2D(p12, p13) + l10 = Arc2D(p13, p14, p15) + l11 = Line2D(p15, p16) + l12 = Arc2D(p16, p17, p18) + l13 = Line2D(p18, p19) # inner web + l14 = Arc2D(p19, p20, p21) + l15 = Line2D(p21, p22) + l16 = Arc2D(p22, p23, p24) + l17 = Line2D(p24, p25) + l18 = Line2D(p25, p26) + l19 = Line2D(p26, p27) + l20 = Arc2D(p27, p28, p1) + + self.curve = PolyCurve2D().by_joined_curves( + [l1, l2, l3, l4, l5, l6, l7, l8, l9, l10, l11, l12, l13, l14, l15, l16, l17, l18, l19, l20]) + + def serialize(self): + return { + 'Description': self.Description, + 'ID': self.ID, + 'id': self.id, + 'type': self.type, + 'name': self.name, + 'h': self.h, + 'b': self.b, + 'h1': self.h1, + 't': self.t, + 'r1': self.r1, + 'r2': self.r2, + 'ex': self.ex, + 'ey': self.ey, + 'curve': self.curve.serialize() if self.curve else None + } + + @staticmethod + def deserialize(data): + c_profile_with_lips = CProfileWithLips( + name=data.get('name'), + b=data.get('b'), + h=data.get('h'), + h1=data.get('h1'), + t=data.get('t'), + r1=data.get('r1'), + ex=data.get('ex') + ) + + c_profile_with_lips.Description = data.get( + 'Description', "Cold Formed C Profile with Lips") + c_profile_with_lips.ID = data.get('ID', "CPWL") + c_profile_with_lips.id = data.get('id') + c_profile_with_lips.type = data.get('type') + c_profile_with_lips.r2 = data.get('r2') + c_profile_with_lips.ey = data.get('ey') + c_profile_with_lips.curve = PolyCurve2D.deserialize( + data['curve']) if 'curve' in data else None + + return c_profile_with_lips + + def __str__(self): + return "Profile(" + f"{self.name})" + + +class LProfileColdFormed: + def __init__(self, name, b, h, t, r1, ex, ey): + self.Description = "Cold Formed L Profile" + self.ID = "CF_L" + + # parameters + self.id = generateID() + self.type = __class__.__name__ + self.name = name + self.curve = [] + self.h = h # height + self.b = b # width + self.t = t # flange thickness + self.r1 = r1 # inner radius + self.r2 = r1-t # outer radius + self.ex = ex + self.ey = ey + r11 = r1/math.sqrt(2) + r2 = r1+t + r21 = r2/math.sqrt(2) + self.IfcProfileDef = "Unknown" + + # describe points + p1 = Point2D(-ex, -ey + r2) # start arc left bottom + p2 = Point2D(-ex + r2 - r21, -ey + r2 - r21) # second point arc + p3 = Point2D(-ex + r2, -ey) # end arc + p4 = Point2D(b - ex, -ey) # right bottom + p5 = Point2D(b - ex, -ey + t) + p6 = Point2D(-ex + t + r1, -ey + t) # start arc + p7 = Point2D(-ex + t + r1 - r11, -ey + t + + r1 - r11) # second point arc + p8 = Point2D(-ex + t, -ey + t + r1) # end arc + p9 = Point2D(-ex + t, ey) + p10 = Point2D(-ex, ey) # left top + + l1 = Arc2D(p1, p2, p3) + l2 = Line2D(p3, p4) + l3 = Line2D(p4, p5) + l4 = Line2D(p5, p6) + l5 = Arc2D(p6, p7, p8) + l6 = Line2D(p8, p9) + l7 = Line2D(p9, p10) + l8 = Line2D(p10, p1) + + self.curve = PolyCurve2D().by_joined_curves( + [l1, l2, l3, l4, l5, l6, l7, l8]) + + def serialize(self): + return { + 'Description': self.Description, + 'ID': self.ID, + 'id': self.id, + 'type': self.type, + 'name': self.name, + 'h': self.h, + 'b': self.b, + 't': self.t, + 'r1': self.r1, + 'r2': self.r2, + 'ex': self.ex, + 'ey': self.ey, + 'curve': self.curve.serialize() if self.curve else None + } + + @staticmethod + def deserialize(data): + l_profile_cold_formed = LProfileColdFormed( + name=data.get('name'), + b=data.get('b'), + h=data.get('h'), + t=data.get('t'), + r1=data.get('r1'), + ex=data.get('ex'), + ey=data.get('ey') + ) + + l_profile_cold_formed.Description = data.get( + 'Description', "Cold Formed L Profile") + l_profile_cold_formed.ID = data.get('ID', "CF_L") + l_profile_cold_formed.id = data.get('id') + l_profile_cold_formed.type = data.get('type') + l_profile_cold_formed.r2 = data.get('r2') + l_profile_cold_formed.curve = PolyCurve2D.deserialize( + data['curve']) if 'curve' in data else None + + return l_profile_cold_formed + + def __str__(self): + return f"{self.type} ({self.name})" + + +class SigmaProfileWithLipsColdFormed: + def __init__(self, name, b, h, t, r1, h1, h2, h3, b2, ex): + self.Description = "Cold Formed Sigma Profile with Lips" + self.ID = "CF_SWL" + + # parameters + self.id = generateID() + self.type = __class__.__name__ + self.name = name + self.curve = [] + self.h = h # height + self.h1 = h1 # LipLength + self.h2 = h2 # MiddleBendLength + self.h3 = h3 # TopBendLength + self.h4 = h4 = (h - h2 - h3 * 2) / 2 + self.h5 = h5 = math.tan(0.5 * math.atan(b2 / h4)) * t + self.b = b # width + self.b2 = b2 # MiddleBendWidth + self.t = t # flange thickness + self.r1 = r1 # inner radius + self.r2 = r2 = r1+t # outer radius + self.ex = ex + self.ey = ey = h/2 + self.r11 = r11 = r1/math.sqrt(2) + self.r21 = r21 = r2/math.sqrt(2) + self.IfcProfileDef = "Unknown" + + p1 = Point2D(-ex + b2, -h2 / 2) + p2 = Point2D(-ex, -ey + h3) + p3 = Point2D(-ex, -ey + r2) # start arc left bottom + p4 = Point2D(-ex + r2 - r21, -ey + r2 - r21) # second point arc + p5 = Point2D(-ex + r2, -ey) # end arc + p6 = Point2D(b - ex - r2, -ey) # start arc + p7 = Point2D(b - ex - r2 + r21, -ey + r2 - r21) # second point arc + p8 = Point2D(b - ex, -ey + r2) # end arc + p9 = Point2D(b - ex, -ey + h1) # end lip + p10 = Point2D(b - ex - t, -ey + h1) + p11 = Point2D(b - ex - t, -ey + t + r1) # start arc + p12 = Point2D(b - ex - t - r1 + r11, -ey + + t + r1 - r11) # second point arc + p13 = Point2D(b - ex - t - r1, -ey + t) # end arc + p14 = Point2D(-ex + t + r1, -ey + t) # start arc + p15 = Point2D(-ex + t + r1 - r11, -ey + t + + r1 - r11) # second point arc + p16 = Point2D(-ex + t, -ey + t + r1) # end arc + p17 = Point2D(-ex + t, -ey + h3 - h5) + p18 = Point2D(-ex + b2 + t, -h2 / 2 - h5) + p19 = Point2D(p18.x, -p18.y) + p20 = Point2D(p17.x, -p17.y) + p21 = Point2D(p16.x, -p16.y) + p22 = Point2D(p15.x, -p15.y) + p23 = Point2D(p14.x, -p14.y) + p24 = Point2D(p13.x, -p13.y) + p25 = Point2D(p12.x, -p12.y) + p26 = Point2D(p11.x, -p11.y) + p27 = Point2D(p10.x, -p10.y) + p28 = Point2D(p9.x, -p9.y) + p29 = Point2D(p8.x, -p8.y) + p30 = Point2D(p7.x, -p7.y) + p31 = Point2D(p6.x, -p6.y) + p32 = Point2D(p5.x, -p5.y) + p33 = Point2D(p4.x, -p4.y) + p34 = Point2D(p3.x, -p3.y) + p35 = Point2D(p2.x, -p2.y) + p36 = Point2D(p1.x, -p1.y) + + l1 = Line2D(p1, p2) + l2 = Line2D(p2, p3) + l3 = Arc2D(p3, p4, p5) + l4 = Line2D(p5, p6) + l5 = Arc2D(p6, p7, p8) + l6 = Line2D(p8, p9) + l7 = Line2D(p9, p10) + l8 = Line2D(p10, p11) + l9 = Arc2D(p11, p12, p13) + l10 = Line2D(p13, p14) + l11 = Arc2D(p14, p15, p16) + l12 = Line2D(p16, p17) + l13 = Line2D(p17, p18) + l14 = Line2D(p18, p19) + l15 = Line2D(p19, p20) + l16 = Line2D(p20, p21) + l17 = Arc2D(p21, p22, p23) + l18 = Line2D(p23, p24) + l19 = Arc2D(p24, p25, p26) + l20 = Line2D(p26, p27) + l21 = Line2D(p27, p28) + l22 = Line2D(p28, p29) + l23 = Arc2D(p29, p30, p31) + l24 = Line2D(p31, p32) + l25 = Arc2D(p32, p33, p34) + l26 = Line2D(p34, p35) + l27 = Line2D(p35, p36) + l28 = Line2D(p36, p1) + + self.curve = PolyCurve2D().by_joined_curves([l1, l2, l3, l4, l5, l6, l7, l8, l9, l10, l11, l12, l13, l14, l15, l16, l17, l18, l19, l20, l21, l22, l23, + l24, l25, + l26, l27, l28]) + + def serialize(self): + return { + 'Description': self.Description, + 'ID': self.ID, + 'id': self.id, + 'type': self.type, + 'name': self.name, + 'h': self.h, + 'b': self.b, + 't': self.t, + 'r1': self.r1, + 'h1': self.h1, + 'h2': self.h2, + 'h3': self.h3, + 'b2': self.b2, + 'ex': self.ex, + 'ey': self.ey, + 'r2': self.r2, + 'r11': self.r11, + 'r21': self.r21, + 'h4': self.h4, + 'h5': self.h5, + 'curve': self.curve.serialize() if self.curve else None + } + + @staticmethod + def deserialize(data): + sigma_profile_with_lips = SigmaProfileWithLipsColdFormed( + name=data.get('name'), + b=data.get('b'), + h=data.get('h'), + t=data.get('t'), + r1=data.get('r1'), + h1=data.get('h1'), + h2=data.get('h2'), + h3=data.get('h3'), + b2=data.get('b2'), + ex=data.get('ex') + ) + + sigma_profile_with_lips.Description = data.get( + 'Description', "Cold Formed Sigma Profile with Lips") + sigma_profile_with_lips.ID = data.get('ID', "CF_SWL") + sigma_profile_with_lips.id = data.get('id') + sigma_profile_with_lips.type = data.get('type') + sigma_profile_with_lips.ey = data.get('ey') + sigma_profile_with_lips.r2 = data.get('r2') + sigma_profile_with_lips.r11 = data.get('r11') + sigma_profile_with_lips.r21 = data.get('r21') + sigma_profile_with_lips.h4 = data.get('h4') + sigma_profile_with_lips.h5 = data.get('h5') + sigma_profile_with_lips.curve = PolyCurve2D.deserialize( + data['curve']) if 'curve' in data else None + + return sigma_profile_with_lips + + def __str__(self): + return f"{self.type} ({self.name})" + + +class ZProfileColdFormed: + def __init__(self, name, b, h, t, r1): + self.Description = "Cold Formed Z Profile" + self.ID = "CF_Z" + + # parameters + self.id = generateID() + self.type = __class__.__name__ + self.name = name + self.curve = [] + self.b = b # width + self.h = h # height + self.t = t # flange thickness + self.r1 = r1 # inner radius + self.r2 = r2 = r1+t # outer radius + self.ex = ex = b/2 + self.ey = ey = h/2 + self.r11 = r11 = r1 / math.sqrt(2) + self.r21 = r21 = r2 / math.sqrt(2) + self.IfcProfileDef = "Unknown" + + p1 = Point2D(-0.5 * t, -ey + t + r1) # start arc + p2 = Point2D(-0.5 * t - r1 + r11, -ey + t + + r1 - r11) # second point arc + p3 = Point2D(-0.5 * t - r1, -ey + t) # end arc + p4 = Point2D(-ex, -ey + t) + p5 = Point2D(-ex, -ey) # left bottom + p6 = Point2D(-r2 + 0.5 * t, -ey) # start arc + p7 = Point2D(-r2 + 0.5 * t + r21, -ey + r2 - r21) # second point arc + p8 = Point2D(0.5 * t, -ey + r2) # end arc + p9 = Point2D(-p1.x, -p1.y) + p10 = Point2D(-p2.x, -p2.y) + p11 = Point2D(-p3.x, -p3.y) + p12 = Point2D(-p4.x, -p4.y) + p13 = Point2D(-p5.x, -p5.y) + p14 = Point2D(-p6.x, -p6.y) + p15 = Point2D(-p7.x, -p7.y) + p16 = Point2D(-p8.x, -p8.y) + + l1 = Arc2D(p1, p2, p3) + l2 = Line2D(p3, p4) + l3 = Line2D(p4, p5) + l4 = Line2D(p5, p6) + l5 = Arc2D(p6, p7, p8) + l6 = Line2D(p8, p9) + l7 = Arc2D(p9, p10, p11) + l8 = Line2D(p11, p12) + l9 = Line2D(p12, p13) + l10 = Line2D(p13, p14) + l11 = Arc2D(p14, p15, p16) + l12 = Line2D(p16, p1) + + self.curve = PolyCurve2D().by_joined_curves( + [l1, l2, l3, l4, l5, l6, l7, l8, l9, l10, l11, l12]) + + def serialize(self): + return { + 'Description': self.Description, + 'ID': self.ID, + 'id': self.id, + 'type': self.type, + 'name': self.name, + 'b': self.b, + 'h': self.h, + 't': self.t, + 'r1': self.r1, + 'r2': self.r2, + 'ex': self.ex, + 'ey': self.ey, + 'r11': self.r11, + 'r21': self.r21, + 'curve': self.curve.serialize() if self.curve else None + } + + @staticmethod + def deserialize(data): + z_profile_cold_formed = ZProfileColdFormed( + name=data.get('name'), + b=data.get('b'), + h=data.get('h'), + t=data.get('t'), + r1=data.get('r1') + ) + + z_profile_cold_formed.Description = data.get( + 'Description', "Cold Formed Z Profile") + z_profile_cold_formed.ID = data.get('ID', "CF_Z") + z_profile_cold_formed.id = data.get('id') + z_profile_cold_formed.type = data.get('type') + z_profile_cold_formed.r2 = data.get('r2') + z_profile_cold_formed.ex = data.get('ex') + z_profile_cold_formed.ey = data.get('ey') + z_profile_cold_formed.r11 = data.get('r11') + z_profile_cold_formed.r21 = data.get('r21') + z_profile_cold_formed.curve = PolyCurve2D.deserialize( + data['curve']) if 'curve' in data else None + + return z_profile_cold_formed + + def __str__(self): + return f"{self.type} ({self.name})" + + +class ZProfileWithLipsColdFormed: + def __init__(self, name, b, h, t, r1, h1): + self.Description = "Cold Formed Z Profile with Lips" + self.ID = "CF_ZL" + + # parameters + self.id = generateID() + self.type = __class__.__name__ + self.name = name + self.curve = [] + self.b = b # width + self.h = h # height + self.t = t # flange thickness + self.h1 = h1 # lip length + self.r1 = r1 # inner radius + self.r2 = r2 = r1+t # outer radius + self.ex = ex = b/2 + self.ey = ey = h/2 + self.r11 = r11 = r1 / math.sqrt(2) + self.r21 = r21 = r2 / math.sqrt(2) + self.IfcProfileDef = "Unknown" + + p1 = Point2D(-0.5*t, -ey+t+r1) # start arc + p2 = Point2D(-0.5*t-r1+r11, -ey+t+r1-r11) # second point arc + p3 = Point2D(-0.5*t-r1, -ey+t) # end arc + p4 = Point2D(-ex+t+r1, -ey+t) # start arc + p5 = Point2D(-ex+t+r1-r11, -ey+t+r1-r11) # second point arc + p6 = Point2D(-ex+t, -ey+t+r1) # end arc + p7 = Point2D(-ex+t, -ey+h1) + p8 = Point2D(-ex, -ey+h1) + p9 = Point2D(-ex, -ey+r2) # start arc + p10 = Point2D(-ex+r2-r21, -ey+r2-r21) # second point arc + p11 = Point2D(-ex+r2, -ey) # end arc + p12 = Point2D(-r2+0.5*t, -ey) # start arc + p13 = Point2D(-r2+0.5*t+r21, -ey+r2-r21) # second point arc + p14 = Point2D(0.5*t, -ey+r2) # end arc + p15 = Point2D(-p1.x, -p1.y) + p16 = Point2D(-p2.x, -p2.y) + p17 = Point2D(-p3.x, -p3.y) + p18 = Point2D(-p4.x, -p4.y) + p19 = Point2D(-p5.x, -p5.y) + p20 = Point2D(-p6.x, -p6.y) + p21 = Point2D(-p7.x, -p7.y) + p22 = Point2D(-p8.x, -p8.y) + p23 = Point2D(-p9.x, -p9.y) + p24 = Point2D(-p10.x, -p10.y) + p25 = Point2D(-p11.x, -p11.y) + p26 = Point2D(-p12.x, -p12.y) + p27 = Point2D(-p13.x, -p13.y) + p28 = Point2D(-p14.x, -p14.y) + + l1 = Arc2D(p1, p2, p3) + l2 = Line2D(p3, p4) + l3 = Arc2D(p4, p5, p6) + l4 = Line2D(p6, p7) + l5 = Line2D(p7, p8) + l6 = Line2D(p8, p9) + l7 = Arc2D(p9, p10, p11) + l8 = Line2D(p11, p12) + l9 = Arc2D(p12, p13, p14) + l10 = Line2D(p14, p15) + l11 = Arc2D(p15, p16, p17) + l12 = Line2D(p17, p18) + l13 = Arc2D(p18, p19, p20) + l14 = Line2D(p20, p21) + l15 = Line2D(p21, p22) + l16 = Line2D(p22, p23) + l17 = Arc2D(p23, p24, p25) + l18 = Line2D(p25, p26) + l19 = Arc2D(p26, p27, p28) + l20 = Line2D(p28, p1) + + self.curve = PolyCurve2D().by_joined_curves( + [l1, l2, l3, l4, l5, l6, l7, l8, l9, l10, l11, l12, l13, l14, l15, l16, l17, l18, l19, l20]) + + def serialize(self): + return { + 'Description': self.Description, + 'ID': self.ID, + 'id': self.id, + 'type': self.type, + 'name': self.name, + 'b': self.b, + 'h': self.h, + 't': self.t, + 'h1': self.h1, + 'r1': self.r1, + 'r2': self.r2, + 'ex': self.ex, + 'ey': self.ey, + 'r11': self.r11, + 'r21': self.r21, + 'curve': self.curve.serialize() if self.curve else None + } + + @staticmethod + def deserialize(data): + z_profile_with_lips = ZProfileWithLipsColdFormed( + name=data.get('name'), + b=data.get('b'), + h=data.get('h'), + t=data.get('t'), + r1=data.get('r1'), + h1=data.get('h1') + ) + + z_profile_with_lips.Description = data.get( + 'Description', "Cold Formed Z Profile with Lips") + z_profile_with_lips.ID = data.get('ID', "CF_ZL") + z_profile_with_lips.id = data.get('id') + z_profile_with_lips.type = data.get('type') + z_profile_with_lips.r2 = data.get('r2') + z_profile_with_lips.ex = data.get('ex') + z_profile_with_lips.ey = data.get('ey') + z_profile_with_lips.r11 = data.get('r11') + z_profile_with_lips.r21 = data.get('r21') + z_profile_with_lips.curve = PolyCurve2D.deserialize( + data['curve']) if 'curve' in data else None + + return z_profile_with_lips + + def __str__(self): + return f"{self.type} ({self.name})" + +class Level: + def __init__(self): + self.id = generateID() + self.type = __class__.__name__ + self.name = None + self.polycurve = None + self.plane = None + self.parms = None + self.elevation = None + self.coordinatesystem: CoordinateSystem = CSGlobal + + @classmethod + def by_point(self, point=Point, name=str): + if point.type == "Point": + Lvl = Level() + XY_plane = [Vector3(x=1, y=0, z=0), Vector3(x=0, y=1, z=0)] + Lvl.plane = Plane.by_two_vectors_origin( + XY_plane[0], XY_plane[1], point) + Lvl.polycurve = Rect_XY(Point.to_vector(point), 1000, 1000) + Lvl.elevation = point.z + if name != None: + Lvl.name = name + return Lvl + elif point.type == "Point2D": + pass # 0 + + def __str__(self) -> str: + return f"{self.type}(Name={self.name}, Elevation={self.elevation})" + +sqrt2 = math.sqrt(2) + +class Tshape: + def __init__(self, name, h, b, h1, b1): + self.Description = "T-shape" + self.ID = "T" + + # parameters + self.id = generateID() + self.type = __class__.__name__ + self.name = name + self.curve = [] + self.h = h # height + self.b = b # width + self.h1 = h1 + self.b1 = b1 + + # describe points + p1 = Point2D(b1 / 2, -h / 2) # right bottom + p2 = Point2D(b1 / 2, h / 2 - h1) # right middle 1 + p3 = Point2D(b / 2, h / 2 - h1) # right middle 2 + p4 = Point2D(b / 2, h / 2) # right top + p5 = Point2D(-b / 2, h / 2) # left top + p6 = Point2D(-b / 2, h / 2 - h1) # left middle 2 + p7 = Point2D(-b1 / 2, h / 2 - h1) # left middle 1 + p8 = Point2D(-b1 / 2, -h / 2) # left bottom + + # describe curves + l1 = Line2D(p1, p2) + l2 = Line2D(p2, p3) + l3 = Line2D(p3, p4) + l4 = Line2D(p4, p5) + l5 = Line2D(p5, p6) + l6 = Line2D(p6, p7) + l7 = Line2D(p7, p8) + l8 = Line2D(p8, p1) + + self.curve = PolyCurve2D().by_joined_curves( + [l1, l2, l3, l4, l5, l6, l7, l8]) + + def serialize(self): + id_value = str(self.id) if not isinstance( + self.id, (str, int, float)) else self.id + return { + 'id': id_value, + 'type': self.type, + 'Description': self.Description, + 'ID': self.ID, + 'name': self.name, + 'h': self.h, + 'b': self.b, + 'h1': self.h1, + 'b1': self.b1, + 'curve': self.curve.serialize() if self.curve else None + } + + @staticmethod + def deserialize(data): + tshape = Tshape( + name=data.get('name'), + h=data.get('h'), + b=data.get('b'), + h1=data.get('h1'), + b1=data.get('b1') + ) + + tshape.Description = data.get('Description', "T-shape") + tshape.ID = data.get('ID', "T") + tshape.curve = PolyCurve2D.deserialize( + data['curve']) if 'curve' in data else None + + return tshape + + def __str__(self): + return "Profile(" + f"{self.name})" + + +class Lshape: + def __init__(self, name, h, b, h1, b1): + self.Description = "L-shape" + self.ID = "L" + + # parameters + self.id = generateID() + self.type = __class__.__name__ + self.name = name + self.curve = [] + self.h = h # height + self.b = b # width + self.h1 = h1 + self.b1 = b1 + + # describe points + p1 = Point2D(b / 2, -h / 2) # right bottom + p2 = Point2D(b / 2, -h / 2 + h1) # right middle + p3 = Point2D(-b / 2 + b1, -h / 2 + h1) # middle + p4 = Point2D(-b / 2 + b1, h / 2) # middle top + p5 = Point2D(-b / 2, h / 2) # left top + p6 = Point2D(-b / 2, -h / 2) # left bottom + + # describe curves + l1 = Line2D(p1, p2) + l2 = Line2D(p2, p3) + l3 = Line2D(p3, p4) + l4 = Line2D(p4, p5) + l5 = Line2D(p5, p6) + l6 = Line2D(p6, p1) + + self.curve = PolyCurve2D().by_joined_curves([l1, l2, l3, l4, l5, l6]) + + def serialize(self): + return { + 'Description': self.Description, + 'ID': self.ID, + 'id': self.id, + 'type': self.type, + 'name': self.name, + 'h': self.h, + 'b': self.b, + 'h1': self.h1, + 'b1': self.b1, + 'curve': self.curve.serialize() if self.curve else None + } + + @staticmethod + def deserialize(data): + lshape = Lshape( + name=data.get('name'), + h=data.get('h'), + b=data.get('b'), + h1=data.get('h1'), + b1=data.get('b1') + ) + + lshape.Description = data.get('Description', "L-shape") + lshape.ID = data.get('ID', "L") + lshape.id = data.get('id') + lshape.type = data.get('type') + lshape.curve = PolyCurve2D.deserialize( + data['curve']) if 'curve' in data else None + + return lshape + + def __str__(self): + return "Profile(" + f"{self.name})" + + +class Eshape: + def __init__(self, name, h, b, h1): + self.Description = "E-shape" + self.ID = "E" + + # parameters + self.id = generateID() + self.type = __class__.__name__ + self.name = name + self.curve = [] + self.h = h # height + self.b = b # width + self.h1 = h1 + + # describe points + p1 = Point2D(b / 2, -h / 2) # right bottom + p2 = Point2D(b / 2, -h / 2 + h1) + p3 = Point2D(-b / 2 + h1, -h / 2 + h1) + p4 = Point2D(-b / 2 + h1, -h1 / 2) + p5 = Point2D(b / 2, -h1 / 2) + p6 = Point2D(b / 2, h1 / 2) + p7 = Point2D(-b / 2 + h1, h1 / 2) + p8 = Point2D(-b / 2 + h1, h / 2 - h1) + p9 = Point2D(b / 2, h / 2 - h1) + p10 = Point2D(b / 2, h / 2) + p11 = Point2D(-b / 2, h / 2) + p12 = Point2D(-b / 2, -h / 2) + + # describe curves + l1 = Line2D(p1, p2) + l2 = Line2D(p2, p3) + l3 = Line2D(p3, p4) + l4 = Line2D(p4, p5) + l5 = Line2D(p5, p6) + l6 = Line2D(p6, p7) + l7 = Line2D(p7, p8) + l8 = Line2D(p8, p9) + l9 = Line2D(p9, p10) + l10 = Line2D(p10, p11) + l11 = Line2D(p11, p12) + l12 = Line2D(p12, p1) + + self.curve = PolyCurve2D().by_joined_curves( + [l1, l2, l3, l4, l5, l6, l7, l8, l9, l10, l11, l12]) + + def serialize(self): + return { + 'Description': self.Description, + 'ID': self.ID, + 'id': self.id, + 'type': self.type, + 'name': self.name, + 'h': self.h, + 'b': self.b, + 'h1': self.h1, + 'curve': self.curve.serialize() if self.curve else None + } + + @staticmethod + def deserialize(data): + eshape = Eshape( + name=data.get('name'), + h=data.get('h'), + b=data.get('b'), + h1=data.get('h1') + ) + + eshape.Description = data.get('Description', "E-shape") + eshape.ID = data.get('ID', "E") + eshape.id = data.get('id') + eshape.type = data.get('type') + eshape.curve = PolyCurve2D.deserialize( + data['curve']) if 'curve' in data else None + + return eshape + + def __str__(self): + return "Profile(" + f"{self.name})" + + +class Nshape: + def __init__(self, name, h, b, b1): + self.Description = "N-shape" + self.ID = "N" + + # parameters + self.id = generateID() + self.type = __class__.__name__ + self.name = name + self.curve = [] + self.h = h # height + self.b = b # width + self.b1 = b1 + + # describe points + p1 = Point2D(b / 2, -h / 2) # right bottom + p2 = Point2D(b / 2, h / 2) + p3 = Point2D(b / 2 - b1, h / 2) + p4 = Point2D(b / 2 - b1, -h / 2 + b1 * 2) + p5 = Point2D(-b / 2 + b1, h / 2) + p6 = Point2D(-b / 2, h / 2) + p7 = Point2D(-b / 2, -h / 2) + p8 = Point2D(-b / 2 + b1, -h / 2) + p9 = Point2D(-b / 2 + b1, h / 2 - b1 * 2) + p10 = Point2D(b / 2 - b1, -h / 2) + + # describe curves + l1 = Line2D(p1, p2) + l2 = Line2D(p2, p3) + l3 = Line2D(p3, p4) + l4 = Line2D(p4, p5) + l5 = Line2D(p5, p6) + l6 = Line2D(p6, p7) + l7 = Line2D(p7, p8) + l8 = Line2D(p8, p9) + l9 = Line2D(p9, p10) + l10 = Line2D(p10, p1) + + self.curve = PolyCurve2D().by_joined_curves( + [l1, l2, l3, l4, l5, l6, l7, l8, l9, l10]) + + def serialize(self): + return { + 'Description': self.Description, + 'ID': self.ID, + 'id': self.id, + 'type': self.type, + 'name': self.name, + 'h': self.h, + 'b': self.b, + 'b1': self.b1, + 'curve': self.curve.serialize() if self.curve else None + } + + @staticmethod + def deserialize(data): + nshape = Nshape( + name=data.get('name'), + h=data.get('h'), + b=data.get('b'), + b1=data.get('b1') + ) + + nshape.Description = data.get('Description', "N-shape") + nshape.ID = data.get('ID', "N") + nshape.id = data.get('id') + nshape.type = data.get('type') + nshape.curve = PolyCurve2D.deserialize( + data['curve']) if 'curve' in data else None + + return nshape + + def __str__(self): + return "Profile(" + f"{self.name})" + + +class Arrowshape: + def __init__(self, name, l, b, b1, l1): + self.Description = "Arrow-shape" + self.ID = "Arrowshape" + + # parameters + self.id = generateID() + self.type = __class__.__name__ + self.name = name + self.curve = [] + self.l = l # length + self.b = b # width + self.b1 = b1 + self.l1 = l1 + + # describe points + p1 = Point2D(0, l / 2) # top middle + p2 = Point2D(b / 2, -l / 2 + l1) + # p3 = Point2D(b1 / 2, -l / 2 + l1) + p3 = Point2D(b1 / 2, (-l / 2 + l1) + (l / 2) / 4) + p4 = Point2D(b1 / 2, -l / 2) + p5 = Point2D(-b1 / 2, -l / 2) + # p6 = Point2D(-b1 / 2, -l / 2 + l1) + p6 = Point2D(-b1 / 2, (-l / 2 + l1) + (l / 2) / 4) + p7 = Point2D(-b / 2, -l / 2 + l1) + + # describe curves + l1 = Line2D(p1, p2) + l2 = Line2D(p2, p3) + l3 = Line2D(p3, p4) + l4 = Line2D(p4, p5) + l5 = Line2D(p5, p6) + l6 = Line2D(p6, p7) + l7 = Line2D(p7, p1) + + self.curve = PolyCurve2D().by_joined_curves( + [l1, l2, l3, l4, l5, l6, l7]) + + def serialize(self): + return { + 'Description': self.Description, + 'ID': self.ID, + 'id': self.id, + 'type': self.type, + 'name': self.name, + 'l': self.l, + 'b': self.b, + 'b1': self.b1, + 'l1': self.l1, + 'curve': self.curve.serialize() if self.curve else None + } + + @staticmethod + def deserialize(data): + arrowshape = Arrowshape( + name=data.get('name'), + l=data.get('l'), + b=data.get('b'), + b1=data.get('b1'), + l1=data.get('l1') + ) + + arrowshape.Description = data.get('Description', "Arrow-shape") + arrowshape.ID = data.get('ID', "Arrowshape") + arrowshape.id = data.get('id') + arrowshape.type = data.get('type') + arrowshape.curve = PolyCurve2D.deserialize( + data['curve']) if 'curve' in data else None + + return arrowshape + + def __str__(self): + return "Profile(" + f"{self.name})" + +class Door: + def __init__(self): + self.id = generateID() + self.type = __class__.__name__ + self.name = None + self.verts = None + self.faces = None + self.topsurface = None + self.bottomsurface = None + # self.polycurve = None or self.profile + self.parms = None + self.coordinatesystem: CoordinateSystem = CSGlobal + self.colorlst = None + + @classmethod + def by_mesh(self, verts=list, faces=list): + door = Door() + door.verts = verts + door.faces = faces + return door + + def __str__(self) -> str: + return f"{self.type}(Name={self.name})" + +def colorlist(extrus, color): + colorlst = [] + for j in range(int(len(extrus.verts) / 3)): + colorlst.append(color) + return (colorlst) + + +# ToDo Na update van color moet ook de colorlist geupdate worden +class Frame: + def __init__(self): + self.id = generateID() + self.type = __class__.__name__ + self.name = "None" + self.profileName = "None" + self.extrusion = None + self.comments = None + self.structuralType = None + self.start = None + self.end = None + self.curve = None # 2D polycurve of the sectionprofile + self.curve3d = None # Translated 3D polycurve of the sectionprofile + self.length = 0 + self.points = [] + self.coordinateSystem: CoordinateSystem = CSGlobal + self.YJustification = "Origin" # Top, Center, Origin, Bottom + self.ZJustification = "Origin" # Left, Center, Origin, Right + self.YOffset = 0 + self.ZOffset = 0 + self.rotation = 0 + self.material = None + self.color = BaseOther.color + self.profile_data = None + self.colorlst = [] + self.vector = None + self.vector_normalised = None + self.centerbottom = None + + def serialize(self): + id_value = str(self.id) if not isinstance( + self.id, (str, int, float)) else self.id + return { + 'id': id_value, + 'type': self.type, + 'name': self.name, + 'profileName': self.profileName, + 'extrusion': self.extrusion, + 'comments': self.comments, + 'structuralType': self.structuralType, + 'start': self.start, + 'end': self.end, + 'curve': self.curve, + 'curve3d': self.curve3d, + 'length': self.length, + 'coordinateSystem': self.coordinateSystem.serialize(), + 'YJustification': self.YJustification, + 'ZJustification': self.ZJustification, + 'YOffset': self.YOffset, + 'ZOffset': self.ZOffset, + 'rotation': self.rotation, + 'material': self.material, + 'color': self.color, + 'colorlst': self.colorlst, + 'vector': self.vector.serialize() if self.vector else None, + 'vector_normalised': self.vector_normalised.serialize() if self.vector_normalised else None + } + + @staticmethod + def deserialize(data): + frame = Frame() + frame.id = data.get('id') + frame.type = data.get('type') + frame.name = data.get('name', "None") + frame.profileName = data.get('profileName', "None") + frame.extrusion = data.get('extrusion') + frame.comments = data.get('comments') + frame.structuralType = data.get('structuralType') + frame.start = data.get('start') + frame.end = data.get('end') + frame.curve = data.get('curve') + frame.curve3d = data.get('curve3d') + frame.length = data.get('length', 0) + frame.coordinateSystem = CoordinateSystem.deserialize( + data['coordinateSystem']) + frame.YJustification = data.get('YJustification', "Origin") + frame.ZJustification = data.get('ZJustification', "Origin") + frame.YOffset = data.get('YOffset', 0) + frame.ZOffset = data.get('ZOffset', 0) + frame.rotation = data.get('rotation', 0) + frame.material = data.get('material') + frame.color = data.get('color', BaseOther.color) + frame.colorlst = data.get('colorlst', []) + frame.vector = Vector3.deserialize( + data['vector']) if 'vector' in data else None + frame.vector_normalised = Vector3.deserialize( + data['vector_normalised']) if 'vector_normalised' in data else None + + return frame + + def props(self): + self.vector = Vector3(self.end.x-self.start.x, + self.end.y-self.start.y, self.end.z-self.start.z) + self.vector_normalised = Vector3.normalize(self.vector) + self.length = Vector3.length(self.vector) + + @classmethod + def by_startpoint_endpoint_profile(cls, start: Union[Point, Node], end: Union[Point, Node], profile: Union[str, PolyCurve2D], name: str, material: None, comments=None): + f1 = Frame() + f1.comments = comments + + if start.type == 'Point': + f1.start = start + elif start.type == 'Node': + f1.start = start.point + if end.type == 'Point': + f1.end = end + elif end.type == 'Node': + f1.end = end.point + + if type(profile).__name__ == "PolyCurve2D": + f1.curve = profile + elif type(profile).__name__ == "Polygon": + f1.curve = PolyCurve2D.by_points(profile.points) + elif type(profile).__name__ == "str": + f1.curve = profiledataToShape(profile).polycurve2d # polycurve2d + f1.points = profiledataToShape(profile).polycurve2d.points + else: + print("[by_startpoint_endpoint_profile], input is not correct.") + sys.exit() + + f1.directionVector = Vector3.by_two_points(f1.start, f1.end) + f1.length = Vector3.length(f1.directionVector) + f1.name = name + f1.extrusion = Extrusion.by_polycurve_height_vector( + f1.curve, f1.length, CSGlobal, f1.start, f1.directionVector) + f1.extrusion.name = name + f1.curve3d = f1.extrusion.polycurve_3d_translated + f1.profileName = profile + f1.material = material + f1.color = material.colorint + f1.colorlst = colorlist(f1.extrusion, f1.color) + f1.props() + return f1 + + @classmethod + def by_startpoint_endpoint_profile_shapevector(cls, start: Union[Point, Node], end: Union[Point, Node], profile_name: str, name: str, vector2d: Vector2, rotation: float, material: None, comments: None): + f1 = Frame() + f1.comments = comments + + if start.type == 'Point': + f1.start = start + elif start.type == 'Node': + f1.start = start.point + if end.type == 'Point': + f1.end = end + elif end.type == 'Node': + f1.end = end.point + + try: + curv = profiledataToShape(profile_name).polycurve2d + except Exception as e: + # Profile does not exist + print(f"Profile does not exist: {profile_name}\nError: {e}") + + f1.rotation = rotation + curvrot = curv.rotate(rotation) # rotation in degrees + f1.curve = curvrot.translate(vector2d) + f1.XOffset = vector2d.x + f1.YOffset = vector2d.y + f1.directionVector = Vector3.by_two_points(f1.start, f1.end) + f1.length = Vector3.length(f1.directionVector) + f1.name = name + f1.extrusion = Extrusion.by_polycurve_height_vector( + f1.curve, f1.length, CSGlobal, f1.start, f1.directionVector) + f1.extrusion.name = name + f1.curve3d = f1.extrusion.polycurve_3d_translated + f1.profileName = profile_name + f1.material = material + f1.color = material.colorint + f1.colorlst = colorlist(f1.extrusion, f1.color) + f1.props() + return f1 + + @classmethod + def by_startpoint_endpoint_profile_justifiction(cls, start: Union[Point, Node], end: Union[Point, Node], profile: Union[str, PolyCurve2D], name: str, XJustifiction: str, YJustifiction: str, rotation: float, material=None, ey: None = float, ez: None = float, structuralType: None = str, comments=None): + f1 = Frame() + f1.comments = comments + + if start.type == 'Point': + f1.start = start + elif start.type == 'Node': + f1.start = start.point + if end.type == 'Point': + f1.end = end + elif end.type == 'Node': + f1.end = end.point + + f1.structuralType = structuralType + f1.rotation = rotation + + if type(profile).__name__ == "PolyCurve2D": + profile_name = "None" + f1.profile_data = profile + curve = f1.profile_data + elif type(profile).__name__ == "Polygon": + profile_name = "None" + f1.profile_data = PolyCurve2D.by_points(profile.points) + curve = f1.profile_data + elif type(profile).__name__ == "str": + profile_name = profile + f1.profile_data = profiledataToShape(profile).polycurve2d # polycurve2d + curve = f1.profile_data + else: + print("[by_startpoint_endpoint_profile], input is not correct.") + sys.exit() + + # curve = f1.profile_data.polycurve2d + + v1 = justifictionToVector(curve, XJustifiction, YJustifiction) # 1 + f1.XOffset = v1.x + f1.YOffset = v1.y + curve = curve.translate(v1) + curve = curve.translate(Vector2(ey, ez)) # 2 + curve = curve.rotate(f1.rotation) # 3 + f1.curve = curve + + f1.directionVector = Vector3.by_two_points(f1.start, f1.end) + f1.length = Vector3.length(f1.directionVector) + f1.name = name + f1.extrusion = Extrusion.by_polycurve_height_vector( + f1.curve, f1.length, CSGlobal, f1.start, f1.directionVector) + f1.extrusion.name = name + f1.curve3d = f1.extrusion.polycurve_3d_translated + + try: + pnew = PolyCurve.by_joined_curves(f1.curve3d.curves) + f1.centerbottom = PolyCurve.centroid(pnew) + except: + pass + + f1.profileName = profile_name + f1.material = material + f1.color = material.colorint + f1.colorlst = colorlist(f1.extrusion, f1.color) + f1.props() + return f1 + + @classmethod + def by_startpoint_endpoint(cls, start: Union[Point, Node], end: Union[Point, Node], polycurve: PolyCurve2D, name: str, rotation: float, material=None, comments=None): + # 2D polycurve + f1 = Frame() + f1.comments = comments + + if start.type == 'Point': + f1.start = start + elif start.type == 'Node': + f1.start = start.point + if end.type == 'Point': + f1.end = end + elif end.type == 'Node': + f1.end = end.point + + f1.directionVector = Vector3.by_two_points(f1.start, f1.end) + f1.length = Vector3.length(f1.directionVector) + f1.name = name + curvrot = polycurve.rotate(rotation) + f1.extrusion = Extrusion.by_polycurve_height_vector( + curvrot, f1.length, CSGlobal, f1.start, f1.directionVector) + f1.extrusion.name = name + f1.curve3d = curvrot + f1.profileName = name + f1.material = material + f1.color = material.colorint + f1.colorlst = colorlist(f1.extrusion, f1.color) + f1.props() + return f1 + + @classmethod + def by_point_height_rotation(cls, start: Union[Point, Node], height: float, polycurve: PolyCurve2D, frame_name: str, rotation: float, material=None, comments=None): + # 2D polycurve + f1 = Frame() + f1.comments = comments + + if start.type == 'Point': + f1.start = start + elif start.type == 'Node': + f1.start = start.point + + f1.end = Point.translate(f1.start, Vector3(0, 0.00001, height)) + + # self.curve = Line(start, end) + f1.directionVector = Vector3.by_two_points(f1.start, f1.end) + f1.length = Vector3.length(f1.directionVector) + f1.name = frame_name + f1.profileName = frame_name + curvrot = polycurve.rotate(rotation) # rotation in degrees + f1.extrusion = Extrusion.by_polycurve_height_vector( + curvrot, f1.length, CSGlobal, f1.start, f1.directionVector) + f1.extrusion.name = frame_name + f1.curve3d = curvrot + f1.material = material + f1.color = material.colorint + f1.colorlst = colorlist(f1.extrusion, f1.color) + f1.props() + return f1 + + @classmethod + def by_point_profile_height_rotation(cls, start: Union[Point, Node], height: float, profile_name: str, rotation: float, material=None, comments=None): + f1 = Frame() + f1.comments = comments + + if start.type == 'Point': + f1.start = start + elif start.type == 'Node': + f1.start = start.point + # TODO vertical column not possible + f1.end = Point.translate(f1.start, Vector3(0, height)) + + # self.curve = Line(start, end) + f1.directionVector = Vector3.by_two_points(f1.start, f1.end) + f1.length = Vector3.length(f1.directionVector) + f1.name = profile_name + f1.profileName = profile_name + curv = profiledataToShape(profile_name).polycurve2d + curvrot = curv.rotate(rotation) # rotation in degrees + f1.extrusion = Extrusion.by_polycurve_height_vector( + curvrot.curves, f1.length, CSGlobal, f1.start, f1.directionVector) + f1.extrusion.name = profile_name + f1.curve3d = curvrot + f1.profileName = profile_name + f1.material = material + f1.color = material.colorint + f1.colorlst = colorlist(f1.extrusion, f1.color) + f1.props() + return f1 + + @classmethod + def by_startpoint_endpoint_curve_justifiction(cls, start: Union[Point, Node], end: Union[Point, Node], polycurve: PolyCurve2D, name: str, XJustifiction: str, YJustifiction: str, rotation: float, material=None, comments=None): + f1 = Frame() + f1.comments = comments + + if start.type == 'Point': + f1.start = start + elif start.type == 'Node': + f1.start = start.point + if end.type == 'Point': + f1.end = end + elif end.type == 'Node': + f1.end = end.point + + f1.rotation = rotation + curv = polycurve + curvrot = curv.rotate(rotation) # rotation in degrees + # center, left, right, origin / center, top bottom, origin + v1 = justifictionToVector(curvrot, XJustifiction, YJustifiction) + f1.XOffset = v1.x + f1.YOffset = v1.y + f1.curve = curv.translate(v1) + f1.directionVector = Vector3.by_two_points(f1.start, f1.end) + f1.length = Vector3.length(f1.directionVector) + f1.name = name + f1.extrusion = Extrusion.by_polycurve_height_vector( + f1.curve.curves, f1.length, CSGlobal, f1.start, f1.directionVector) + f1.extrusion.name = name + f1.profileName = "none" + f1.material = material + f1.color = material.colorint + f1.colorlst = colorlist(f1.extrusion, f1.color) + f1.props() + return f1 + + def write(self, project): + project.objects.append(self) + return self +# EVERYWHERE FOR EACH OBJECT A ROTATION/POSITION +# Make sure that the objects can be merged! + +class WurksRaster3d: + def __init__(self): + self.id = generateID() + self.type = __class__.__name__ + self.bottom = None + self.top = None + self.name = "x" + self.lines = None + + def serialize(self): + id_value = str(self.id) if not isinstance( + self.id, (str, int, float)) else self.id + return { + 'id': id_value, + 'type': self.type, + 'bottom': self.bottom.serialize() if self.bottom else None, + 'top': self.top.serialize() if self.top else None, + 'name': self.name, + 'lines': [line.serialize() for line in self.lines] if self.lines else None + } + + @staticmethod + def deserialize(data): + wurks_raster3d = WurksRaster3d() + wurks_raster3d.id = data.get('id') + wurks_raster3d.type = data.get('type') + wurks_raster3d.bottom = Surface.deserialize( + data['bottom']) if 'bottom' in data else None + wurks_raster3d.top = Surface.deserialize( + data['top']) if 'top' in data else None + wurks_raster3d.name = data.get('name', "x") + + if 'lines' in data and data['lines'] is not None: + wurks_raster3d.lines = [PolyCurve.deserialize( + line_data) for line_data in data['lines']] + else: + wurks_raster3d.lines = None + + return wurks_raster3d + + def by_line(self, lines: Line, bottom: float, top: float): + self.bottom = Vector3(0, 0, bottom) + self.top = Vector3(0, 0, top) + self.lines = lines + + surfList = [] + for line in self.lines: + pts = [] + pts.append(Point.translate(line.start, self.bottom)) + pts.append(Point.translate(line.end, self.bottom)) + pts.append(Point.translate(line.end, self.top)) + pts.append(Point.translate(line.start, self.top)) + project.objects.append(Surface(PolyCurve.by_points(pts))) + surfList.append(Surface(PolyCurve.by_points(pts))) + + print(f"{len(surfList)}* {self.__class__.__name__} {project.createdTxt}") + + +class WurksPedestal: + def __init__(self): + self.topfilename = "temp\\jonathan\\pedestal_top.dxf" + self.basefilename = "temp\\jonathan\\pedestal_foot.dxf" + self.diameter = 10 + self.topheight = 3 + self.baseheight = 3 + self.cache = {} + self.top_dxf = None + self.base_dxf = None + + def load_dxf(self, filename): + if filename in self.cache: + return self.cache[filename] + else: + dxf = ReadDXF(filename).polycurve + self.cache[filename] = dxf + return dxf + + def load_top_dxf(self): + if self.top_dxf is None: + self.top_dxf = self.load_dxf(self.topfilename) + return self.top_dxf + + def load_base_dxf(self): + if self.base_dxf is None: + self.base_dxf = self.load_dxf(self.basefilename) + return self.base_dxf + + def by_point(self, points, height, rotation=None): + if isinstance(points, Point): + points = [points] + + top = self.load_top_dxf() + base = self.load_base_dxf() + + for point in points: + topcenter = Point.difference(top.centroid(), point) + translated_top = top.translate(Point.to_vector(topcenter)) + project.objects.append(Extrusion.by_polycurve_height( + translated_top, self.topheight, 0)) + + frame = Rect( + Vector3(x=(translated_top.centroid().x) - (self.diameter / 2), + y=(translated_top.centroid().y) - (self.diameter / 2), + z=point.z - self.topheight), + self.diameter, self.diameter + ) + project.objects.append(Extrusion.by_polycurve_height( + frame, height - self.baseheight - self.topheight, 0)) + + basecenter = Point.difference(base.centroid(), point) + translated_base = base.translate(Point.to_vector(basecenter)) + project.objects.append(Extrusion.by_polycurve_height( + translated_base, self.baseheight, -height)) + + print(f"{len(points)}* {self.__class__.__name__} {project.createdTxt}") + + pass # pootje, voet diameter(vierkant), verstelbare hoogte inregelen, + + +class WurksComputerFloor(): # centerpoint / rotation / panel pattern / ply + pass # some type of floor object + + +class WurksFloorFinish(): + pass # direction / pattern / ect + + +class WorkPlane(): + def __init__(self): + self.length = None + self.width = None + self.points = [] + + def create(self, length: float = None, width: float = None) -> str: + self.length = length or 1000 + self.width = width or 1000 + rect = Rect(Vector3(0, 0, 0), self.length, self.width) + for pt in rect.points: + self.points.append(pt) + project.objects.append(rect) + print(f"1* {self.__class__.__name__} {project.createdTxt}") + return Rect(Vector3(0, 0, 0), self.length, self.width) + + pass # pootje, voet diameter(vierkant), verstelbare hoogte inregelen, + + +WorkPlane = WorkPlane() +# rotation(Vector3)/#volume/#scale + + +class Support: + def __init__(self): + self.Number = None + self.Point: Point = Point(0, 0, 0) + self.id = generateID() + self.type = __class__.__name__ + self.Tx: str = " " # A, P, N, S + self.Ty: str = " " # A, P, N, S + self.Tz: str = " " # A, P, N, S + self.Rx: str = " " # A, P, N, S + self.Ry: str = " " # A, P, N, S + self.Rz: str = " " # A, P, N, S + self.Kx: float = 0 # kN/m + self.Ky: float = 0 # kN/m + self.Kz: float = 0 # kN/m + self.Cx: float = 0 # kNm/rad + self.Cy: float = 0 # kNm/rad + self.Cz: float = 0 # kNm/rad + self.dx: float = 0 # eccentricity in x + self.dy: float = 0 # eccentricity in y + self.dz: float = 0 # eccentricity in z + + def serialize(self): + return { + 'Number': self.Number, + 'Point': self.Point.serialize(), + 'type': self.type, + 'Tx': self.Tx, + 'Ty': self.Ty, + 'Tz': self.Tz, + 'Rx': self.Rx, + 'Ry': self.Ry, + 'Rz': self.Rz, + 'Kx': self.Kx, + 'Ky': self.Ky, + 'Kz': self.Kz, + 'Cx': self.Cx, + 'Cy': self.Cy, + 'Cz': self.Cz, + 'dx': self.dx, + 'dy': self.dy, + 'dz': self.dz + } + + @staticmethod + def deserialize(data): + support = Support() + support.Number = data.get('Number') + support.Point = Point.deserialize(data['Point']) + support.Tx = data.get('Tx', " ") + support.Ty = data.get('Ty', " ") + support.Tz = data.get('Tz', " ") + support.Rx = data.get('Rx', " ") + support.Ry = data.get('Ry', " ") + support.Rz = data.get('Rz', " ") + support.Kx = data.get('Kx', 0) + support.Ky = data.get('Ky', 0) + support.Kz = data.get('Kz', 0) + support.Cx = data.get('Cx', 0) + support.Cy = data.get('Cy', 0) + support.Cz = data.get('Cz', 0) + support.dx = data.get('dx', 0) + support.dy = data.get('dy', 0) + support.dz = data.get('dz', 0) + + return support + + @staticmethod + def pinned(PlacementPoint): + sup = Support() + sup.Point = PlacementPoint + sup.Tx = "A" + sup.Ty = "A" + sup.Tz = "A" + return (sup) + + @staticmethod + def x_roller(PlacementPoint): + sup = Support() + sup.Point = PlacementPoint + sup.Ty = "A" + sup.Tz = "A" + return (sup) + + @staticmethod + def y_roller(PlacementPoint): + sup = Support() + sup.Point = PlacementPoint + sup.Tx = "A" + sup.Tz = "A" + return (sup) + + @staticmethod + def z_roller(PlacementPoint): + sup = Support() + sup.Point = PlacementPoint + sup.Tx = "A" + sup.Ty = "A" + return (sup) + + @staticmethod + def fixed(PlacementPoint): + sup = Support() + sup.Point = PlacementPoint + sup.Tx = "A" + sup.Ty = "A" + sup.Tz = "A" + sup.Rx = "A" + sup.Ry = "A" + sup.Rz = "A" + return (sup) + + +class LoadCase: + def __init__(self): + self.Number = None + self.Description: str = "" + self.psi0 = 1 + self.psi1 = 1 + self.psi2 = 1 + self.Type = 0 # 0 = permanent, 1 = variabel + + +class SurfaceLoad: + def __init__(self): + self.LoadCase = None + self.PolyCurve: PolyCurve = None + self.Description: str = "" + self.crs = "ccaa0435161960d4c7e436cf107a03f61" + self.direction = "caf2b4ce743de1df30071f9566b1015c6" + self.LoadBearingDirection = "cfebf3fce7063ab9a89d28a86508c0fb3" + self.q1 = 0 + self.q2 = 0 + self.q3 = 0 + self.LoadConstantOrLinear = "cb81ae405e988f21166edf06d7fd646fb" + self.iq1 = -1 + self.iq2 = -1 + self.iq3 = -1 + + @staticmethod + def by_load_case_polycurve_q(LoadCase, PolyCurve, q): + SL = SurfaceLoad() + SL.LoadCase = LoadCase + SL.PolyCurve = PolyCurve + SL.q1 = q + SL.q2 = q + SL.q3 = q + return SL + + +class LoadPanel: + def __init__(self): + self.PolyCurve: PolyCurve = None + self.Description: str = "" + self.LoadBearingDirection = "X" + # Wall, saddle_roof_positive_pitch #Wall, / Free-standing wall, Flat roof, Shed roof, Saddle roof, Unknown + self.SurfaceType = "" + + +def chess_board_surface_loads_rectangle(startx, starty, dx, dy, nx, ny, width, height, LoadCase, q123, description: str): + SurfaceLoads = [] + x = startx + y = starty + for j in range(ny): + for i in range(nx): + SL = SurfaceLoad() + SL.Description = description + SL.LoadCase = LoadCase + SL.PolyCurve = PolyCurve.by_points( + [Point(x, y, 0), + Point(x + width, y, 0), + Point(x, y + height, 0), + Point(x, y, 0)] + ) + SL.q1 = SL.q2 = SL.q3 = q123 # [kN/m2] + SurfaceLoads.append(SL) + x = x + dx + y = y + dy + return SurfaceLoads + + +class Floor: + def __init__(self): + self.id = generateID() + self.type = __class__.__name__ + self.extrusion = None + self.thickness = 0 + self.name = None + self.description = None + self.perimeter: float = 0 + self.coordinatesystem: CoordinateSystem = CSGlobal + self.colorint = None + self.colorlst = [] + self.origincurve = None + self.points = None + self.thickness = None + +class TickMark: + # Dimension Tick Mark + def __init__(self): + self.name = None + self.id = generateID() + self.curves = [] + + @staticmethod + def by_curves(name, curves): + TM = TickMark() + TM.name = name + TM.curves = curves + return TM + + +TMDiagonal = TickMark.by_curves( + "diagonal", [Line(start=Point(-100, -100, 0), end=Point(100, 100, 0))]) + + +class DimensionType: + def __init__(self): + self.name = None + self.id = generateID() + self.type = __class__.__name__ + self.font = None + self.text_height = 2.5 + self.tick_mark: TickMark = TMDiagonal + self.line_extension = 100 + + def serialize(self): + return { + 'name': self.name, + 'id': self.id, + 'type': self.type, + 'font': self.font, + 'text_height': self.text_height, + 'tick_mark': str(self.tick_mark), + 'line_extension': self.line_extension + } + + @staticmethod + def deserialize(data): + dimension_type = DimensionType() + dimension_type.name = data.get('name') + dimension_type.id = data.get('id') + dimension_type.type = data.get('type') + dimension_type.font = data.get('font') + dimension_type.text_height = data.get('text_height', 2.5) + + # Handle TickMark deserialization + tick_mark_str = data.get('tick_mark') + # Adjust according to your TickMark implementation + dimension_type.tick_mark = TickMark(tick_mark_str) + + dimension_type.line_extension = data.get('line_extension', 100) + + return dimension_type + + @staticmethod + def by_name_font_textheight_tick_mark_extension(name: str, font: str, text_height: float, tick_mark: TickMark, line_extension: float): + DT = DimensionType() + DT.name = name + DT.font = font + DT.text_height = text_height + DT.tick_mark = tick_mark + DT.line_extension = line_extension + return DT + + +DT2_5_mm = DimensionType.by_name_font_textheight_tick_mark_extension( + "2.5 mm", "calibri", 2.5, TMDiagonal, 100) + +DT1_8_mm = DimensionType.by_name_font_textheight_tick_mark_extension( + "1.8 mm", "calibri", 2.5, TMDiagonal, 100) + + +class Dimension: + def __init__(self, start: Point, end: Point, dimension_type) -> None: + self.id = generateID() + self.type = __class__.__name__ + self.start: Point = start + self.text_height = 100 + self.end: Point = end + self.scale = 0.1 # text + self.dimension_type: DimensionType = dimension_type + self.curves = [] + self.length: float = Line(start=self.start, end=self.end).length + self.text = None + self.geom() + + def serialize(self): + return { + 'type': self.type, + 'start': self.start.serialize(), + 'end': self.end.serialize(), + 'text_height': self.text_height, + 'id': self.id, + 'scale': self.scale, + 'dimension_type': self.dimension_type.serialize(), + 'curves': [curve.serialize() for curve in self.curves], + 'length': self.length, + 'text': self.text + } + + @staticmethod + def deserialize(data): + start = Point.deserialize(data['start']) + end = Point.deserialize(data['end']) + dimension_type = DimensionType.deserialize(data['dimension_type']) + dimension = Dimension(start, end, dimension_type) + + dimension.text_height = data.get('text_height', 100) + dimension.id = data.get('id') + dimension.scale = data.get('scale', 0.1) + dimension.curves = [Line.deserialize( + curve_data) for curve_data in data.get('curves', [])] + dimension.length = data.get('length') + dimension.text = data.get('text') + + return dimension + + @staticmethod + def by_startpoint_endpoint_offset(start: Point, end: Point, dimension_type: DimensionType, offset: float): + DS = Dimension() + DS.start = start + DS.end = end + DS.dimension_type = dimension_type + DS.geom() + return DS + + def geom(self): + # baseline + baseline = Line(start=self.start, end=self.end) + midpoint_text = baseline.mid_point() + direction = Vector3.normalize(baseline.vector) + tick_mark_extension_point_1 = Point.translate(self.start, Vector3.reverse( + Vector3.scale(direction, self.dimension_type.line_extension))) + tick_mark_extension_point_2 = Point.translate( + self.end, Vector3.scale(direction, self.dimension_type.line_extension)) + x = direction + y = Vector3.rotate_XY(x, math.radians(90)) + z = Z_Axis + cs_new_start = CoordinateSystem(self.start, x, y, z) + cs_new_mid = CoordinateSystem(midpoint_text, x, y, z) + cs_new_end = CoordinateSystem(self.end, x, y, z) + self.curves.append(Line(tick_mark_extension_point_1, + self.start)) # extention_start + self.curves.append( + Line(tick_mark_extension_point_2, self.end)) # extention_end + self.curves.append(Line(self.start, self.end)) # baseline + # erg vieze oplossing. #Todo + crvs = Line( + start=self.dimension_type.tick_mark.curves[0].start, end=self.dimension_type.tick_mark.curves[0].end) + + self.curves.append(Line.transform( + self.dimension_type.tick_mark.curves[0], cs_new_start)) # dimension tick start + self.curves.append(Line.transform(crvs, cs_new_end) + ) # dimension tick end + self.text = Text(text=str(round(self.length)), font_family=self.dimension_type.font, + cs=cs_new_mid, height=self.text_height).write() + + def write(self, project): + for i in self.curves: + project.objects.append(i) + for j in self.text: + project.objects.append(j) + + +class FrameTag: + def __init__(self): + # Dimensions in 1/100 scale + self.id = generateID() + self.type = __class__.__name__ + self.scale = 0.1 + self.cs: CoordinateSystem = CSGlobal + self.offset_x = 500 + self.offset_y = 100 + self.font_family = "calibri" + self.text: str = "text" + self.text_curves = None + self.text_height = 100 + + def serialize(self): + id_value = str(self.id) if not isinstance( + self.id, (str, int, float)) else self.id + return { + 'id': id_value, + 'type': self.type, + 'scale': self.scale, + 'cs': self.cs.serialize(), + 'offset_x': self.offset_x, + 'offset_y': self.offset_y, + 'font_family': self.font_family, + 'text': self.text, + 'text_curves': self.text_curves, + 'text_height': self.text_height + } + + @staticmethod + def deserialize(data): + frame_tag = FrameTag() + frame_tag.scale = data.get('scale', 0.1) + frame_tag.cs = CoordinateSystem.deserialize(data['cs']) + frame_tag.offset_x = data.get('offset_x', 500) + frame_tag.offset_y = data.get('offset_y', 100) + frame_tag.font_family = data.get('font_family', "calibri") + frame_tag.text = data.get('text', "text") + frame_tag.text_curves = data.get('text_curves') + frame_tag.text_height = data.get('text_height', 100) + + return frame_tag - #### Effects: - - Generates Frame objects for each division, placing them vertically within the rectangle system. - - Populates `inner_frame_objects` with these Frame instances. - - Adds symbolic representations of these frames to `symbolic_inner_grids`. - """ - for i in self.division_system.distances: - start_point = Point.translate( - self.mother_surface_origin_point_x_zero, Vector(i, 0, 0)) - end_point = Point.translate( - self.mother_surface_origin_point_x_zero, Vector(i, self.inner_height, 0)) - self.inner_frame_objects.append( - Frame.by_start_point_endpoint_curve_justifiction( - start_point, end_point, self.inner_frame_type.curve, "innerframe", "center", "top", 0, self.material) - ) - self.symbolic_inner_grids.append( - Line(start=start_point, end=end_point)) + def __textobject(self): + cstext = self.cs + # cstextnew = cstext.translate(self.textoff_vector_local) + self.text_curves = Text( + text=self.text, font_family=self.font_family, height=self.text_height, cs=cstext).write - def __outer_frames(self): - """Generates the outer frame objects for the rectangle system. - Creates Frame objects for the bottom, top, left, and right boundaries of the rectangle system. Each frame is defined by its start and end points, along with its type and material. Symbolic lines representing these frames are also generated for visualization. + def by_cs_text(self, coordinate_system: CoordinateSystem, text): + self.cs = coordinate_system + self.text = text + self.__textobject() + return self - #### Effects: - - Creates Frame instances for the outer boundaries of the rectangle system and adds them to `outer_frame_objects`. - - Generates symbolic Line instances for each outer frame and adds them to `symbolic_outer_grids`. - """ - bottomframe = Frame.by_start_point_endpoint_curve_justifiction(Point(0, 0, 0), Point( - self.width, 0, 0), self.bottom_frame_type.curve, "bottomframe", "left", "top", 0, self.material) - self.symbolic_outer_grids.append( - Line(start=Point(0, 0, 0), end=Point(self.width, 0, 0))) + def write(self, project): + for x in self.text_curves(): + project.objects.append(x) + return self - topframe = Frame.by_start_point_endpoint_curve_justifiction(Point(0, self.height, 0), Point( - self.width, self.height, 0), self.top_frame_type.curve, "bottomframe", "right", "top", 0, self.material) - self.symbolic_outer_grids.append( - Line(start=Point(0, self.height, 0), end=Point(self.width, self.height, 0))) + @staticmethod + def by_frame(frame): + tag = FrameTag() + frame_vector = frame.vector_normalised + x = frame_vector + y = Vector3.rotate_XY(x, math.radians(90)) + z = Z_Axis + vx = Vector3.scale(frame_vector, tag.offset_x) + frame_width = PolyCurve2D.bounds(frame.curve)[4] + vy = Vector3.scale(y, frame_width*0.5+tag.offset_y) + origintext = Point.translate(frame.start, vx) + origintext = Point.translate(origintext, vy) + csnew = CoordinateSystem(origintext, x, y, z) + tag.cs = csnew + tag.text = frame.name + tag.__textobject() + return tag - leftframe = Frame.by_start_point_endpoint_curve_justifiction(Point(0, self.bottom_frame_type.b, 0), Point( - 0, self.height-self.top_frame_type.b, 0), self.left_frame_type.curve, "leftframe", "right", "top", 0, self.material) - self.symbolic_outer_grids.append(Line(start=Point( - 0, self.bottom_frame_type.b, 0), end=Point(0, self.height-self.top_frame_type.b, 0))) - rightframe = Frame.by_start_point_endpoint_curve_justifiction(Point(self.width, self.bottom_frame_type.b, 0), Point( - self.width, self.height-self.top_frame_type.b, 0), self.right_frame_type.curve, "leftframe", "left", "top", 0, self.material) - self.symbolic_outer_grids.append(Line(start=Point(self.width, self.bottom_frame_type.b, 0), end=Point( - self.width, self.height-self.top_frame_type.b, 0))) +class ColumnTag: + def __init__(self): + # Dimensions in 1/100 scale + self.id = generateID() + self.type = __class__.__name__ + self.width = 700 + self.height = 500 + self.factor = 3 # hellingsfacor leader + self.scale = 0.1 # voor tekeningverschaling + self.position = "TL" # TL, TR, BL, BR Top Left Top Right Bottom Left Bottom Right + self.cs: CoordinateSystem = CSGlobal - self.outer_frame_objects.append(bottomframe) - self.outer_frame_objects.append(topframe) - self.outer_frame_objects.append(leftframe) - self.outer_frame_objects.append(rightframe) + # self.textoff_vector_local: Vector3 = Vector3(1,1,1) + self.font_family = "calibri" + self.curves = [] + # self.leadercurves() + self.text: str = "text" + self.text_height = 100 + self.text_offset_factor = 5 + self.textoff_vector_local: Vector3 = Vector3( + self.height/self.factor, self.height+self.height/self.text_offset_factor, 0) + self.text_curves = None + # self.textobject() - def by_width_height_divisionsystem_studtype(self, width: float, height: float, frame_width: float, frame_height: float, division_system: DivisionSystem, filling: bool) -> 'RectangleSystem': - """Configures the rectangle system with specified dimensions, division system, and frame types. - This method sets the dimensions of the rectangle system, configures the frame types based on the provided dimensions, and applies a division system to generate inner frames. Optionally, it can also fill the system with panels based on the inner divisions. + def serialize(self): + id_value = str(self.id) if not isinstance( + self.id, (str, int, float)) else self.id + return { + 'id': id_value, + 'type': self.type, + 'width': self.width, + 'height': self.height, + 'factor': self.factor, + 'scale': self.scale, + 'position': self.position, + 'cs': self.cs.serialize(), + 'font_family': self.font_family, + 'curves': [curve.serialize() for curve in self.curves], + 'text': self.text, + 'text_height': self.text_height, + 'text_offset_factor': self.text_offset_factor, + 'textoff_vector_local': self.textoff_vector_local.serialize(), + 'text_curves': self.text_curves + } - #### Parameters: - - `width` (float): The width of the rectangle system. - - `height` (float): The height of the rectangle system. - - `frame_width` (float): The width of the frame elements. - - `frame_height` (float): The height (thickness) of the frame elements. - - `division_system` (DivisionSystem): The division system to apply for inner divisions. - - `filling` (bool): A flag indicating whether to fill the divided areas with panels. + @staticmethod + def deserialize(data): + column_tag = ColumnTag() + column_tag.width = data.get('width', 700) + column_tag.height = data.get('height', 500) + column_tag.factor = data.get('factor', 3) + column_tag.scale = data.get('scale', 0.1) + column_tag.position = data.get('position', "TL") + column_tag.cs = CoordinateSystem.deserialize(data['cs']) + column_tag.font_family = data.get('font_family', "calibri") + column_tag.curves = [Line.deserialize( + curve_data) for curve_data in data.get('curves', [])] + column_tag.text = data.get('text', "text") + column_tag.text_height = data.get('text_height', 100) + column_tag.text_offset_factor = data.get('text_offset_factor', 5) + column_tag.textoff_vector_local = Vector3.deserialize( + data['textoff_vector_local']) + column_tag.text_curves = data.get('text_curves') - #### Returns: - `RectangleSystem`: The instance itself, updated with the new configuration. + return column_tag - #### Example usage: - ```python - rectangle_system = RectangleSystem() - rectangle_system.by_width_height_divisionsystem_studtype(2000, 3000, 38, 184, divisionSystem, True) - ``` - """ - self.width = width - self.height = height - self.bottom_frame_type = Rectangle( - "bottom_frame_type", frame_width, frame_height) - self.top_frame_type = Rectangle( - "top_frame_type", frame_width, frame_height) - self.left_frame_type = Rectangle( - "left_frame_type", frame_width, frame_height) - self.right_frame_type = Rectangle( - "left_frame_type", frame_width, frame_height) - self.inner_frame_type = Rectangle( - "inner_frame_type", frame_width, frame_height) - self.division_system = division_system - self.__inner_mother_surface() - self.__inner_frames() - self.__outer_frames() - if filling: - self.__inner_panels() - else: - pass - return self + def __leadercurves(self): + self.startpoint = Point(0, 0, 0) + self.midpoint = Point.translate(self.startpoint, Vector3( + self.height/self.factor, self.height, 0)) + self.endpoint = Point.translate( + self.midpoint, Vector3(self.width, 0, 0)) + crves = [Line(start=self.startpoint, end=self.midpoint), + Line(start=self.midpoint, end=self.endpoint)] + for i in crves: + j = Line.transform(i, self.cs) + self.curves.append(j) + + def __textobject(self): + cstext = self.cs + cstextnew = CoordinateSystem.translate( + cstext, self.textoff_vector_local) + self.text_curves = Text(text=self.text, font_family=self.font_family, + height=self.text_height, cs=cstextnew).write -class pattern_system: - """The `pattern_system` class is designed to define and manipulate patterns for architectural or design applications. It is capable of generating various patterns based on predefined or dynamically generated parameters.""" - def __init__(self): - """Initializes a new pattern_system instance.""" - self.name = None - self.id = generateID() - self.pattern = None - self.basepanels = [] # contains a list with basepanels of the system - # contains a list sublists with Vector which represent the repetition of the system - self.vectors = [] + def by_cs_text(self, coordinate_system: CoordinateSystem, text): + self.cs = coordinate_system + self.text = text + self.__leadercurves() + self.__textobject() + return self - def stretcher_bond_with_joint(self, name: str, brick_width: float, brick_length: float, brick_height: float, joint_width: float, joint_height: float): - """Configures a stretcher bond pattern with joints for the pattern_system. - Establishes the fundamental vectors and base panels for a stretcher bond, taking into account brick dimensions and joint sizes. This pattern alternates bricks in each row, offsetting them by half a brick length. + def write(self, project): + for x in self.text_curves(): + project.objects.append(x) + for y in self.curves: + project.objects.append(y) - #### Parameters: - - `name` (str): Name of the pattern configuration. - - `brick_width` (float): Width of the brick. - - `brick_length` (float): Length of the brick. - - `brick_height` (float): Height of the brick. - - `joint_width` (float): Width of the joint between bricks. - - `joint_height` (float): Height of the joint between brick layers. + @staticmethod + def by_frame(frame, position="TL"): + tag = ColumnTag() + csold = CSGlobal + tag.position = position + tag.cs = CoordinateSystem.translate(csold, Vector3( + frame.start.x, frame.start.y, frame.start.z)) + tag.text = frame.name + tag.__leadercurves() + tag.__textobject() + return tag - #### Returns: - The instance itself, updated with the stretcher bond pattern configuration. - - #### Example usage: - ```python +# class Label: +# class LabelType: +# class TextType: - ``` - """ - self.name = name - # Vectors of panel 1 - V1 = Vector(0, (brick_height + joint_height)*2, 0) # dy - V2 = Vector(brick_length+joint_width, 0, 0) # dx - self.vectors.append([V1, V2]) +def rgb_to_int(rgb): + r, g, b = [max(0, min(255, c)) for c in rgb] - # Vectors of panel 2 - V3 = Vector(0, (brick_height + joint_height) * 2, 0) # dy - V4 = Vector(brick_length + joint_width, 0, 0) # dx - self.vectors.append([V3, V4]) + return (255 << 24) | (r << 16) | (g << 8) | b - dx = (brick_length+joint_width)/2 - dy = brick_height+joint_height +class Material: + def __init__(self): + self.name = "none" + self.color = None + self.colorint = None - PC1 = PolyCurve().by_points([Point(0, 0, 0), Point(0, brick_height, 0), Point( - brick_length, brick_height, 0), Point(brick_length, 0, 0), Point(0, 0, 0)]) - PC2 = PolyCurve().by_points([Point(dx, dy, 0), Point(dx, brick_height+dy, 0), Point( - brick_length+dx, brick_height+dy, 0), Point(brick_length+dx, dy, 0), Point(dx, dy, 0)]) - BasePanel1 = Panel.by_polycurve_thickness( - PC1, brick_width, 0, "BasePanel1", BaseBrick.colorint) - BasePanel2 = Panel.by_polycurve_thickness( - PC2, brick_width, 0, "BasePanel2", BaseBrick.colorint) + @classmethod + def byNameColor(cls, name, color): + M1 = Material() + M1.name = name + M1.color = color + M1.colorint = rgb_to_int(color) + return M1 - self.basepanels.append(BasePanel1) - self.basepanels.append(BasePanel2) - return self - def tile_bond_with_joint(self, name: str, tile_width: float, tile_height: float, tile_thickness: float, joint_width: float, joint_height: float): - """Configures a tile bond pattern with specified dimensions and joint sizes for the pattern_system. - Defines a simple tiling pattern where tiles are laid out in rows and columns, separated by specified joint widths and heights. This method sets up base panels to represent individual tiles and their arrangement vectors. +#Building Materials +BaseConcrete = Material.byNameColor("Concrete", Color().RGB([192, 192, 192])) +BaseTimber = Material.byNameColor("Timber", Color().RGB([191, 159, 116])) +BaseSteel = Material.byNameColor("Steel", Color().RGB([237, 28, 36])) +BaseOther = Material.byNameColor("Other", Color().RGB([150, 150, 150])) +BaseBrick = Material.byNameColor("Brick", Color().RGB([170, 77, 47])) +BaseBrickYellow = Material.byNameColor("BrickYellow", Color().RGB([208, 187, 147])) - #### Parameters: - - `name` (str): The name of the tile bond pattern configuration. - - `tile_width` (float): The width of a single tile. - - `tile_height` (float): The height of a single tile. - - `tile_thickness` (float): The thickness of the tile. - - `joint_width` (float): The width of the joint between adjacent tiles. - - `joint_height` (float): The height of the joint between tile rows. +#GIS Materials +BaseBuilding = Material.byNameColor("Building", Color().RGB([150, 28, 36])) +BaseWater = Material.byNameColor("Water", Color().RGB([139, 197, 214])) +BaseGreen = Material.byNameColor("Green", Color().RGB([175, 193, 138])) +BaseInfra = Material.byNameColor("Infra", Color().RGB([234, 234, 234])) +BaseRoads = Material.byNameColor("Infra", Color().RGB([140, 140, 140])) - #### Returns: - The instance itself, updated with the tile bond pattern configuration. +#class MaterialfinishjsonFile = "https://raw.githubusercontent.com/3BMLabs/Project-Ocondat/master/steelprofile.json" +url = urllib.request.urlopen(jsonFile) +data = json.loads(url.read()) - #### Example Usage: - ```python - pattern_system = pattern_system() - pattern_system.tile_bond_with_joint('TilePattern', 200, 300, 10, 5, 5) - ``` - This configures the `pattern_system` with a tile bond pattern named 'TilePattern', where each tile measures 200x300x10 units, with 5 units of spacing between tiles. - """ + +class searchProfile: + def __init__(self, name): self.name = name - # Vectors of panel 1 - V1 = Vector(0, (tile_height + joint_height), 0) # dy - V2 = Vector(tile_width+joint_width, 0, 0) # dx - self.vectors.append([V1, V2]) + self.shape_coords = None + self.shape_name = None + self.synonyms = None + for item in data: + for i in item.values(): + synonymList = i[0]["synonyms"] + #if self.name in synonymList: + #bools = [self.name.lower() in e for e in [synonym.lower() for synonym in synonymList]] + #if True in bools: + if self.name.lower() in [synonym.lower() for synonym in synonymList]: + self.shape_coords = i[0]["shape_coords"] + self.shape_name = i[0]["shape_name"] + self.synonyms = i[0]["synonyms"] - PC1 = PolyCurve().by_points([Point(0, 0, 0), Point(0, tile_height, 0), Point( - tile_width, tile_height, 0), Point(tile_width, 0, 0)]) - BasePanel1 = Panel.by_polycurve_thickness( - PC1, tile_thickness, 0, "BasePanel1", BaseBrick.colorint) - self.basepanels.append(BasePanel1) - return self +class profiledataToShape: + def __init__(self, name1, segmented = True): + from geometry.curve import PolyCurve + profile_data = searchProfile(name1) + if profile_data == None: + print(f"profile {name1} not recognised") + shape_name = profile_data.shape_name + if shape_name == None: + profile_data = searchProfile(project.structural_fallback_element) + err = f"Error, profile '{name1}' not recognised, define in {jsonFile} | fallback: '{project.structural_fallback_element}'" + print(err) + shape_name = profile_data.shape_name + self.profile_data = profile_data + self.shape_name = shape_name + name = profile_data.name + self.d1 = profile_data.shape_coords + #self.d1.insert(0,name) + d1 = self.d1 + if shape_name == "C-channel parallel flange": + prof = CChannelParallelFlange(name,d1[0],d1[1],d1[2],d1[3],d1[4],d1[5]) + elif shape_name == "C-channel sloped flange": + prof = CChannelSlopedFlange(name,d1[0],d1[1],d1[2],d1[3],d1[4],d1[5],d1[6],d1[7],d1[8]) + elif shape_name == "I-shape parallel flange": + prof = IShapeParallelFlange(name,d1[0],d1[1],d1[2],d1[3],d1[4]) + elif shape_name == "I-shape sloped flange": + prof = IShapeParallelFlange(name, d1[0], d1[1], d1[2], d1[3], d1[4]) + #Todo: add sloped flange shape + elif shape_name == "Rectangle": + prof = Rectangle(name,d1[0], d1[1]) + elif shape_name == "Round": + prof = Round(name, d1[1]) + elif shape_name == "Round tube profile": + prof = Roundtube(name, d1[0], d1[1]) + elif shape_name == "LAngle": + prof = LAngle(name,d1[0],d1[1],d1[2],d1[3],d1[4],d1[5],d1[6],d1[7]) + elif shape_name == "TProfile": + prof = TProfile(name, d1[0], d1[1], d1[2], d1[3], d1[4], d1[5], d1[6], d1[7], d1[8]) + elif shape_name == "Rectangle Hollow Section": + prof = RectangleHollowSection(name,d1[0],d1[1],d1[2],d1[3],d1[4]) + self.prof = prof + self.data = d1 + pc2d = self.prof.curve # 2D polycurve + if segmented == True: + pc3d = PolyCurve.by_polycurve_2D(pc2d) + pcsegment = PolyCurve.segment(pc3d, 10) + pc2d2 = pcsegment.to_polycurve_2D() + else: + pc2d2 = pc2d + self.polycurve2d = pc2d2 - def cross_bond_with_joint(self, name: str, brick_width: float, brick_length: float, brick_height: float, joint_width: float, joint_height: float): - """Configures a cross bond pattern with joints for the pattern_system. - Sets up a complex brick laying pattern combining stretcher (lengthwise) and header (widthwise) bricks in alternating rows, creating a cross bond appearance. This method defines the base panels and their positioning vectors to achieve the cross bond pattern. +def justifictionToVector(plycrv2D: PolyCurve2D, XJustifiction, Yjustification, ey=None, ez=None): + + # print(XJustifiction) + xval = [] + yval = [] + for i in plycrv2D.curves: + xval.append(i.start.x) + yval.append(i.start.y) - #### Parameters: - - `name` (str): The name of the cross bond pattern configuration. - - `brick_width` (float): The width of a single brick. - - `brick_length` (float): The length of the brick. - - `brick_height` (float): The height of the brick layer. - - `joint_width` (float): The width of the joint between bricks. - - `joint_height` (float): The height of the joint between brick layers. + #Boundingbox2D + xmin = min(xval) + xmax = max(xval) + ymin = min(yval) + ymax = max(yval) - #### Returns: - The instance itself, updated with the cross bond pattern configuration. + b = xmax-xmin + h = ymax-ymin - #### Example Usage: - ```python - pattern_system = pattern_system() - pattern_system.cross_bond_with_joint('CrossBondPattern', 90, 190, 80, 10, 10) - ``` - In this configuration, `pattern_system` is set to a cross bond pattern named 'CrossBondPattern', with bricks measuring 90x190x80 units and 10 units of joint spacing in both directions. - """ - self.name = name - lagenmaat = brick_height + joint_height - # Vectors of panel 1 (strek) - V1 = Vector(0, (brick_height + joint_height) * 4, 0) # dy spacing - V2 = Vector(brick_length + joint_width, 0, 0) # dx spacing - self.vectors.append([V1, V2]) + # print(b, h) - # Vectors of panel 2 (koppen 1) - V3 = Vector(0, (brick_height + joint_height) * 2, 0) # dy spacing - V4 = Vector(brick_length + joint_width, 0, 0) # dx spacing - self.vectors.append([V3, V4]) + dxleft = -xmax + dxright = -xmin + dxcenter = dxleft - 0.5 * b #CHECK + dxorigin = 0 - dx2 = (brick_width + joint_width)/2 # start x offset - dy2 = lagenmaat # start y offset + dytop = -ymax + dybottom = -ymin + dycenter = dytop - 0.5 * h #CHECK + dyorigin = 0 - # Vectors of panel 3 (strekken) - V5 = Vector(0, (brick_height + joint_height) * 4, 0) # dy spacing - V6 = Vector(brick_length + joint_width, 0, 0) # dx spacing - self.vectors.append([V5, V6]) + if XJustifiction == "center": + dx = dxorigin #TODO + elif XJustifiction == "left": + dx = dxleft + elif XJustifiction == "right": + dx = dxright + elif XJustifiction == "origin": + dx = dxorigin #TODO + else: + dx = 0 - dx3 = (brick_length + joint_width)/2 # start x offset - dy3 = lagenmaat * 2 # start y offset + if Yjustification == "center": + dy = dyorigin #TODO + elif Yjustification == "top": + dy = dytop + elif Yjustification == "bottom": + dy = dybottom + elif Yjustification == "origin": + dy = dyorigin #TODO + else: + dy = 0 - # Vectors of panel 4 (koppen 2) - V7 = Vector(0, (brick_height + joint_height) * 2, 0) # dy spacing - V8 = Vector(brick_length + joint_width, 0, 0) # dx spacing - self.vectors.append([V7, V8]) + # print(dx, dy) + v1 = Vector2(dx, dy) + # v1 = Vector2(0, 0) - dx4 = (brick_width + joint_width)/2 + \ - (brick_width + joint_width) # start x offset - dy4 = lagenmaat # start y offset + return v1 - PC1 = PolyCurve().by_points([Point(0, 0, 0), Point(0, brick_height, 0), Point( - brick_length, brick_height, 0), Point(brick_length, 0, 0), Point(0, 0, 0)]) - PC2 = PolyCurve().by_points([Point(dx2, dy2, 0), Point(dx2, brick_height+dy2, 0), Point( - brick_width+dx2, brick_height+dy2, 0), Point(brick_width+dx2, dy2, 0), Point(dx2, dy2, 0)]) - PC3 = PolyCurve().by_points([Point(dx3, dy3, 0), Point(dx3, brick_height+dy3, 0), Point( - brick_length+dx3, brick_height+dy3, 0), Point(brick_length+dx3, dy3, 0), Point(dx3, dy3, 0)]) - PC4 = PolyCurve().by_points([Point(dx4, dy4, 0), Point(dx4, brick_height+dy4, 0), Point( - brick_width+dx4, brick_height+dy4, 0), Point(brick_width+dx4, dy4, 0), Point(dx4, dy4, 0)]) +def find_in_list_of_list(mylist, char): + for sub_list in mylist: + if char in sub_list: + return (mylist.index(sub_list)) + raise ValueError("'{char}' is not in list".format(char=char)) - BasePanel1 = Panel.by_polycurve_thickness( - PC1, brick_width, 0, "BasePanel1", BaseBrick.colorint) - BasePanel2 = Panel.by_polycurve_thickness( - PC2, brick_width, 0, "BasePanel2", BaseBrick.colorint) - BasePanel3 = Panel.by_polycurve_thickness( - PC3, brick_width, 0, "BasePanel3", BaseBrick.colorint) - BasePanel4 = Panel.by_polycurve_thickness( - PC4, brick_width, 0, "BasePanel4", BaseBrickYellow.colorint) +class generateID: + def __init__(self) -> None: + self.id = None + self.object = None + self.name = None + self.generateID() - self.basepanels.append(BasePanel1) - self.basepanels.append(BasePanel2) - self.basepanels.append(BasePanel3) - self.basepanels.append(BasePanel4) + def generateID(self) -> None: + id = "" + lengthID = 12 + random_source = string.ascii_uppercase + string.digits + for x in range(lengthID): + id += random.choice(random_source) - return self + id_list = list(id) + self.id = f"#"+"".join(id_list) + return f"test {self.__class__.__name__}" + + def __repr__(self) -> str: + return f"{self.id}" -def pattern_geom(pattern_system, width: float, height: float, start_point: Point = None) -> list[Panel]: - """Generates a geometric pattern based on a pattern_system within a specified area. - Takes a pattern_system and fills a defined width and height area starting from an optional start point with the pattern described by the system. +def findjson(id, json_string): + #faster way to search in json + results = [] + + def _decode_dict(a_dict): + try: + results.append(a_dict[id]) + except KeyError: + pass + return a_dict - #### Parameters: - - `pattern_system`: The pattern_system instance defining the pattern. - - `width` (float): Width of the area to fill with the pattern. - - `height` (float): Height of the area to fill with the pattern. - - `start_point` (Point, optional): Starting point for the pattern generation. + json.loads(json_string, object_hook=_decode_dict) # Return value ignored. + return results - #### Returns: - `list[Panel]`: A list of Panel instances constituting the generated pattern. - - #### Example usage: - ```python +def list_transpose(lst): + #list of lists, transpose columns/rows + newlist = list(map(list, zip(*lst))) + return newlist - ``` - """ - start_point = start_point or Point(0, 0, 0) - test = pattern_system - panels = [] +def is_null(lst): + return all(el is None for el in lst) - for i, j in zip(test.basepanels, test.vectors): - ny = int(height / (j[0].y)) # number of panels in y-direction - nx = int(width / (j[1].x)) # number of panels in x-direction - PC = i.origincurve - thickness = i.thickness - color = i.colorint +def clean_list(input_list, preserve_indices=True): + if not input_list: + return input_list + + culled_list = [] - # YX ARRAY - yvectdisplacement = j[0] - yvector = Point.to_vector(start_point) - xvectdisplacement = j[1] - xvector = Vector(0, 0, 0) + if preserve_indices: + if is_null(input_list): + return None + + j = len(input_list) - 1 + while j >= 0 and input_list[j] is None: + j -= 1 - ylst = [] - for k in range(ny): - yvector = yvectdisplacement + yvector - for l in range(nx): - # Copy in x-direction - xvector = xvectdisplacement + xvector - xyvector = yvector + xvector - # translate curve in x and y-direction - PCNew = PolyCurve.copy_translate(PC, xyvector) - pan = Panel.by_polycurve_thickness( - PCNew, thickness, 0, "name", color) - panels.append(pan) - xvector = Vector.sum( - xvectdisplacement, Vector(-test.basepanels[0].origincurve.curves[1].length, 0, 0)) - return panels + for i in range(j + 1): + sublist = input_list[i] + if isinstance(sublist, list): + val = clean_list(sublist, preserve_indices) + culled_list.append(val) + else: + culled_list.append(input_list[i]) + else: + if is_null(input_list): + return [] + + for el in input_list: + if isinstance(el, list): + if not is_null(el): + val = clean_list(el, preserve_indices=False) + if val: + culled_list.append(val) + elif el is not None: + culled_list.append(el) + + return culled_list -def fillin(perimeter: PolyCurve2D, pattern: pattern_geom) -> pattern_system: - """Fills in a given perimeter with a specified pattern. - Uses a bounding box to define the perimeter within which a pattern is applied, based on a geometric pattern generation function. +def flatten(lst): + if type(lst) != list: + lst = [lst] + flat_list = [] + for sublist in lst: + try: + for item in sublist: + flat_list.append(item) + except: + flat_list.append(sublist) + return flat_list - #### Parameters: - - `perimeter` (PolyCurve2D): The 2D perimeter to fill in. - - `pattern` (function): The pattern generation function to apply within the perimeter. +def all_true(lst): + for element in lst: + if not element: + return False + return True - #### Returns: - `pattern_system`: A pattern_system object that represents the filled-in area. - - #### Example usage: - ```python +def replace_at_index(object, index, new_object): + if index < 0 or index >= len(object): + raise IndexError("Index out of range") + return object[:index] + new_object + object[index+1:] - ``` - """ +def xmldata(myurl, xPathStrings): + urlFile = urllib.request.urlopen(myurl) + tree = ET.parse(urlFile) + xPathResults = [] + for xPathString in xPathStrings: + a = tree.findall(xPathString) + xPathResulttemp2 = [] + for xPathResult in a: + xPathResulttemp2.append(xPathResult.text) + xPathResults.append(xPathResulttemp2) + return xPathResults +COMMANDS = set("MmZzLlHhVvCcSsQqTtAa") +UPPERCASE = set("MZLHVCSQTA") - bb = Rect().by_points(perimeter.points) +COMMAND_RE = re.compile(r"([MmZzLlHhVvCcSsQqTtAa])") +FLOAT_RE = re.compile(rb"^[-+]?\d*\.?\d*(?:[eE][-+]?\d+)?") - for pt in bb.corners: - project.objects.append(pt) - bb_perimeter = PolyCurve.by_points(bb.corners) - return [bb_perimeter] +class InvalidPathError(ValueError): + pass -class Matrix(Serializable, list[list]): - """ - elements are ordered like [row][column] or [y][x] - """ - def __init__(self, matrix:list[list]=[[1, 0], [0, 1]]) -> 'Matrix': - list.__init__(self, matrix) - - @property - def cols(self) -> 'int': - """returns the width (x size) of this matrix in columns.""" - return len(self[0]) +# The argument sequences from the grammar, made sane. +# u: Non-negative number +# s: Signed number or coordinate +# c: coordinate-pair, which is two coordinates/numbers, separated by whitespace +# f: A one character flag, doesn't need whitespace, 1 or 0 +ARGUMENT_SEQUENCE = { + "M": "c", + "Z": "", + "L": "c", + "H": "s", + "V": "s", + "C": "ccc", + "S": "cc", + "Q": "cc", + "T": "c", + "A": "uusffc", +} - @property - def rows(self) -> 'int': - """returns the height (y size) of this matrix in columns.""" - return len(self) - @staticmethod - def scale(dimensions: int, scalar: float)-> 'Matrix': - - match dimensions: - case 1: - arr = [[scalar]] - case 2: - arr = [[scalar,0], - [0,scalar]] - case 3: - arr = [[scalar, 0, 0], - [0, scalar, 0], - [0, 0, scalar]] - case 4: - arr= [[scalar, 0, 0, 0], - [0, scalar, 0, 0], - [0, 0, scalar, 0], - [0, 0, 0, scalar]] - return Matrix(arr) - - @staticmethod - def empty(rows:int, cols = None): - """creates a matrix of size n x m (rows x columns or y * x or h * w)""" - if cols == None: - cols = rows - return Matrix([[0 for col in range(cols)] for row in range(rows)]) +def strip_array(arg_array): + """Strips whitespace and commas""" + # EBNF wsp:(#x20 | #x9 | #xD | #xA) + comma: 0x2C + while arg_array and arg_array[0] in (0x20, 0x9, 0xD, 0xA, 0x2C): + arg_array[0:1] = b"" - @staticmethod - def identity(dimensions:int): - return Matrix.scale(dimensions, 1) - @staticmethod - def translate(toAdd: Vector): - dimensions:int = len(toAdd) + 1 - return Matrix([[1 if x == y else toAdd[y] if x == len(toAdd) else 0 for x in range(dimensions)] for y in range(len(toAdd))]) - - def __mul__(self, other:Self | Coords | Line | Rect | PointList): - """CAUTION! MATRICES NEED TO MULTIPLY FROM RIGHT TO LEFT! - for example: translate * rotate (rotate first, translate after) - and: matrix * point (point first, multiplied by matrix after)""" - if isinstance(other, Matrix): - #multiply matrices with eachother - #https://www.geeksforgeeks.org/multiplication-two-matrices-single-line-using-numpy-python/ +def pop_number(arg_array): + res = FLOAT_RE.search(arg_array) + if not res or not res.group(): + raise InvalidPathError(f"Expected a number, got '{arg_array}'.") + number = float(res.group()) + start = res.start() + end = res.end() + arg_array[start:end] = b"" + strip_array(arg_array) - #visualisation of resulting sizes: - #https://en.wikipedia.org/wiki/Matrix_multiplication + return number - #the number of columns (width) in the first matrix needs to be equal to the number of rows (height) in the second matrix - #(look at for i in range(other.height)) - #we are multiplying row vectors of self with col vectors of other - if self.cols == other.rows: - resultRows = self.rows - resultCols = other.cols - result:Matrix = Matrix.empty(resultRows, resultCols) - # explicit for loops - for row in range(self.rows): - for col in range(other.cols): - for multiplyIndex in range(other.rows): - #this is the simple code, which would work if the number of self.cols was equal to other.rows - result[row][col] += self[row][multiplyIndex] * other[multiplyIndex][col] - else: - resultCols = max(self.cols, other.cols) - resultRows = max(self.rows, other.rows) +def pop_unsigned_number(arg_array): + number = pop_number(arg_array) + if number < 0: + raise InvalidPathError(f"Expected a non-negative number, got '{number}'.") + return number - result:Matrix = Matrix.empty(resultRows, resultCols) - #the size of the vector that we're multiplying. - multiplyVectorSize = max(self.cols, other.rows) +def pop_coordinate_pair(arg_array): + x = pop_number(arg_array) + y = pop_number(arg_array) + return complex(x, y) - # explicit for loops - for row in range(resultRows): - for col in range(resultCols): - for multiplyIndex in range(multiplyVectorSize): - #if an element doesn't exist in the matrix, we use an identity element. - selfValue = self[row][multiplyIndex] if row < self.rows and multiplyIndex < self.cols else 1 if multiplyIndex == row else 0 - otherValue = other[multiplyIndex][col] if col < other.cols and multiplyIndex < other.rows else 1 if multiplyIndex == col else 0 - result[row][col] += selfValue * otherValue - elif isinstance(other, PointList): - return other.__class__([self * p for p in other]) - #point comes in from top and comes out to the right: - # | - # v - #a b - #c d -> - elif isinstance(other, Coords): - result: Coords = Coords([0] * self.rows) - #loop over column vectors and multiply them with the vector. sum the results (multiplied col 1 + multiplied col 2) to get the final product! - for col in range(self.cols): - if col < len(other): - for row in range(self.rows): - result[row] += self[row][col] * other[col] - else: - #otherValue = 1, just add the vector - for row in range(self.rows): - result[row] += self[row][col] - return result - elif isinstance(other, Line): - return Line(self * other.start, self * other.end) - elif isinstance(other, Rect): - mp0 = self * other.p0 - mp1 = self * other.p1 - return Rect.by_points([mp0, mp1]) - return result - - transform = multiply = __mul__ +def pop_flag(arg_array): + flag = arg_array[0] + arg_array[0:1] = b"" + strip_array(arg_array) + if flag == 48: # ASCII 0 + return False + if flag == 49: # ASCII 1 + return True - def add(self, other: 'Matrix'): - if self.shape() != other.shape(): - raise ValueError("Matrices must have the same dimensions") - return Matrix([[self[i][j] + other.matrix[i][j] for j in range(len(self[0]))] for i in range(len(self))]) - def all(self, axis=None): - if axis is None: - return all(all(row) for row in self) - elif axis == 0: - return [all(self[row][col] for row in range(len(self))) for col in range(len(self[0]))] - elif axis == 1: - return [all(col) for col in self] - else: - raise ValueError("Axis must be None, 0, or 1") +FIELD_POPPERS = { + "u": pop_unsigned_number, + "s": pop_number, + "c": pop_coordinate_pair, + "f": pop_flag, +} - def any(self, axis=None): - if axis is None: - return any(any(row) for row in self) - elif axis == 0: - return [any(self[row][col] for row in range(len(self))) for col in range(len(self[0]))] - elif axis == 1: - return [any(col) for col in self] - else: - raise ValueError("Axis must be None, 0, or 1") - def argmax(self, axis=None): - if axis is None: - flat_list = [item for sublist in self for item in sublist] - return flat_list.index(max(flat_list)) - elif axis == 0: - return [max(range(len(self)), key=lambda row: self[row][col]) for col in range(len(self[0]))] - elif axis == 1: - return [max(range(len(row)), key=lambda col: row[col]) for row in self] - else: - raise ValueError("Axis must be None, 0, or 1") +def _commandify_path(pathdef): + """Splits path into commands and arguments""" + token = None + for x in COMMAND_RE.split(pathdef): + x = x.strip() + if x in COMMANDS: + if token is not None: + yield token + if x in ("z", "Z"): + # The end command takes no arguments, so add a blank one + token = (x, "") + else: + token = (x,) + elif x: + if token is None: + raise InvalidPathError(f"Path does not start with a command: {pathdef}") + token += (x,) + yield token - def argmin(self, axis=None): - if axis is None: - flat_list = [item for sublist in self for item in sublist] - return flat_list.index(min(flat_list)) - elif axis == 0: - return [min(range(len(self)), key=lambda row: self[row][col]) for col in range(len(self[0]))] - elif axis == 1: - return [min(range(len(row)), key=lambda col: row[col]) for row in self] - else: - raise ValueError("Axis must be None, 0, or 1") - def argpartition(self, kth, axis=0): - def partition(arr, kth): - pivot = arr[kth] - less = [i for i in range(len(arr)) if arr[i] < pivot] - equal = [i for i in range(len(arr)) if arr[i] == pivot] - greater = [i for i in range(len(arr)) if arr[i] > pivot] - return less + equal + greater +def _tokenize_path(pathdef): + for command, args in _commandify_path(pathdef): + # Shortcut this for the close command, that doesn't have arguments: + if command in ("z", "Z"): + yield (command,) + continue - if axis == 0: - return [partition([self[row][col] for row in range(len(self))], kth) for col in range(len(self[0]))] - elif axis == 1: - return [partition(row, kth) for row in self] + # For the rest of the commands, we parse the arguments and + # yield one command per full set of arguments + arg_sequence = ARGUMENT_SEQUENCE[command.upper()] + arguments = bytearray(args, "ascii") + implicit = False + while arguments: + command_arguments = [] + for i, arg in enumerate(arg_sequence): + try: + command_arguments.append(FIELD_POPPERS[arg](arguments)) + except InvalidPathError as e: + if i == 0 and implicit: + return # Invalid character in path, treat like a comment + raise InvalidPathError( + f"Invalid path element {command} {args}" + ) from e - def argsort(self, axis=0): - if axis == 0: - return [[row for row, val in sorted(enumerate(col), key=lambda x: x[1])] for col in zip(*self)] - elif axis == 1: - return [list(range(len(self[0]))) for _ in self] + yield (command,) + tuple(command_arguments) + implicit = True - def astype(self, dtype): - cast_matrix = [[dtype(item) for item in row] for row in self] - return Matrix(cast_matrix) + # Implicit Moveto commands should be treated as Lineto commands. + if command == "m": + command = "l" + elif command == "M": + command = "L" - def byteswap(self, inplace=False): - if inplace: - for i in range(len(self)): - for j in range(len(self[i])): - self[i][j] = ~self[i][j] - return self - else: - new_matrix = [[~item for item in row] for row in self] - return Matrix(new_matrix) - def choose(self, choices, mode='raise'): - if mode != 'raise': - raise NotImplementedError("Only 'raise' mode is implemented") +def parse_path(pathdef): + segments = path.Path() + start_pos = None + last_command = None + current_pos = 0 - chosen = [[choices[item] for item in row] for row in self] - return Matrix(chosen) + for token in _tokenize_path(pathdef): + command = token[0] + relative = command.islower() + command = command.upper() + if command == "M": + pos = token[1] + if relative: + current_pos += pos + else: + current_pos = pos + segments.append(path.Move(current_pos, relative=relative)) + start_pos = current_pos - def compress(self, condition, axis=None): - if axis == 0: - compressed = [row for row, cond in zip( - self, condition) if cond] - return Matrix(compressed) - else: - raise NotImplementedError("Axis other than 0 is not implemented") + elif command == "Z": + # For Close commands the "relative" argument just preserves case, + # it has no different in behavior. + segments.append(path.Close(current_pos, start_pos, relative=relative)) + current_pos = start_pos - def clip(self, min=None, max=None): - clipped_matrix = [] - for row in self: - clipped_row = [max if max is not None and val > - max else min if min is not None and val < min else val for val in row] - clipped_matrix.append(clipped_row) - return Matrix(clipped_matrix) + elif command == "L": + pos = token[1] + if relative: + pos += current_pos + segments.append(path.Line(current_pos, pos, relative=relative)) + current_pos = pos - def conj(self): - conjugated_matrix = [[complex(item).conjugate() - for item in row] for row in self] - return Matrix(conjugated_matrix) + elif command == "H": + hpos = token[1] + if relative: + hpos += current_pos.real + pos = complex(hpos, current_pos.imag) + segments.append( + path.Line(current_pos, pos, relative=relative, horizontal=True) + ) + current_pos = pos - def conjugate(self): - return self.conj() + elif command == "V": + vpos = token[1] + if relative: + vpos += current_pos.imag + pos = complex(current_pos.real, vpos) + segments.append( + path.Line(current_pos, pos, relative=relative, vertical=True) + ) + current_pos = pos - def copy(self): - copied_matrix = copy.deepcopy(self) - return Matrix(copied_matrix) + elif command == "C": + control1 = token[1] + control2 = token[2] + end = token[3] - def cumprod(self, axis=None): - if axis is None: - flat_list = self.flatten() - cumprod_list = [] - cumprod = 1 - for item in flat_list: - cumprod *= item - cumprod_list.append(cumprod) - return Matrix([cumprod_list]) - else: - raise NotImplementedError( - "Axis handling not implemented in this example") + if relative: + control1 += current_pos + control2 += current_pos + end += current_pos + + segments.append( + path.CubicBezier( + current_pos, control1, control2, end, relative=relative + ) + ) + current_pos = end - def cumsum(self, axis=None): - if axis is None: - flat_list = self.flatten() - cumsum_list = [] - cumsum = 0 - for item in flat_list: - cumsum += item - cumsum_list.append(cumsum) - return Matrix([cumsum_list]) - else: - raise NotImplementedError( - "Axis handling not implemented in this example") + elif command == "S": + # Smooth curve. First control point is the "reflection" of + # the second control point in the previous path. + control2 = token[1] + end = token[2] - def diagonal(self, offset=0): - return [self[i][i + offset] for i in range(len(self)) if 0 <= i + offset < len(self[i])] + if relative: + control2 += current_pos + end += current_pos - def dump(self, file): - with open(file, 'wb') as f: - pickle.dump(self, f) + if last_command in "CS": + # The first control point is assumed to be the reflection of + # the second control point on the previous command relative + # to the current point. + control1 = current_pos + current_pos - segments[-1].control2 + else: + # If there is no previous command or if the previous command + # was not an C, c, S or s, assume the first control point is + # coincident with the current point. + control1 = current_pos - def dumps(self): - return pickle.dumps(self) + segments.append( + path.CubicBezier( + current_pos, control1, control2, end, relative=relative, smooth=True + ) + ) + current_pos = end - def fill(self, value): - for i in range(len(self)): - for j in range(len(self[i])): - self[i][j] = value + elif command == "Q": + control = token[1] + end = token[2] - @staticmethod - def from_points(from_point: Point, to_point: Point): - Vz = Vector.by_two_points(from_point, to_point) - Vz = Vector.normalize(Vz) - Vzglob = Vector(0, 0, 1) - Vx = Vector.cross_product(Vz, Vzglob) - if Vector.length(Vx) == 0: - Vx = Vector(1, 0, 0) if Vz.x != 1 else Vector(0, 1, 0) - Vx = Vector.normalize(Vx) - Vy = Vector.cross_product(Vx, Vz) + if relative: + control += current_pos + end += current_pos - return Matrix([ - [Vx.x, Vy.x, Vz.x, from_point.x], - [Vx.y, Vy.y, Vz.y, from_point.y], - [Vx.z, Vy.z, Vz.z, from_point.z], - [0, 0, 0, 1] - ]) + segments.append( + path.QuadraticBezier(current_pos, control, end, relative=relative) + ) + current_pos = end - def flatten(self): - return [item for sublist in self for item in sublist] + elif command == "T": + # Smooth curve. Control point is the "reflection" of + # the second control point in the previous path. + end = token[1] - def getA(self): - return self + if relative: + end += current_pos - def getA1(self): - return [item for sublist in self for item in sublist] + if last_command in "QT": + # The control point is assumed to be the reflection of + # the control point on the previous command relative + # to the current point. + control = current_pos + current_pos - segments[-1].control + else: + # If there is no previous command or if the previous command + # was not an Q, q, T or t, assume the first control point is + # coincident with the current point. + control = current_pos - def getH(self): - conjugate_transposed = [[complex(self[j][i]).conjugate() for j in range( - len(self))] for i in range(len(self[0]))] - return Matrix(conjugate_transposed) + segments.append( + path.QuadraticBezier( + current_pos, control, end, smooth=True, relative=relative + ) + ) + current_pos = end - def getI(self): - raise NotImplementedError( - "Matrix inversion is a complex operation not covered in this simple implementation.") + elif command == "A": + # For some reason I implemented the Arc with a complex radius. + # That doesn't really make much sense, but... *shrugs* + radius = complex(token[1], token[2]) + rotation = token[3] + arc = token[4] + sweep = token[5] + end = token[6] - def getT(self): - return self.transpose() + if relative: + end += current_pos - def getfield(self, dtype, offset=0): - raise NotImplementedError( - "This method is conceptual and depends on structured data support within the Matrix.") + segments.append( + path.Arc( + current_pos, radius, rotation, arc, sweep, end, relative=relative + ) + ) + current_pos = end - def item(self, *args): - if len(args) == 1: - index = args[0] - rows, cols = len(self), len(self[0]) - return self[index // cols][index % cols] - elif len(args) == 2: - return self[args[0]][args[1]] - else: - raise ValueError("Invalid number of indices.") + # Finish up the loop in preparation for next command + last_command = command - def itemset(self, *args): - if len(args) == 2: - index, value = args - rows, cols = len(self), len(self[0]) - self[index // cols][index % cols] = value - elif len(args) == 3: - row, col, value = args - self[row][col] = value - else: - raise ValueError("Invalid number of arguments.") + return segments - def max(self, axis=None): - if axis is None: - return max(item for sublist in self for item in sublist) - elif axis == 0: - return [max(self[row][col] for row in range(len(self))) for col in range(len(self[0]))] - elif axis == 1: - return [max(row) for row in self] - else: - raise ValueError("Invalid axis.") +MIN_DEPTH = 5 +ERROR = 1e-12 - def mean(self, axis=None): - if axis is None: - flat_list = self.flatten() - return sum(flat_list) / len(flat_list) - elif axis == 0: - return [sum(self[row][col] for row in range(len(self))) / len(self) for col in range(len(self[0]))] - elif axis == 1: - return [sum(row) / len(row) for row in self] - else: - raise ValueError("Axis must be None, 0, or 1") - def min(self, axis=None): - if axis is None: - return min(item for sublist in self for item in sublist) - elif axis == 0: - return [min(self[row][col] for row in range(len(self))) for col in range(len(self[0]))] - elif axis == 1: - return [min(row) for row in self] - else: - raise ValueError("Invalid axis.") +def segment_length(curve, start, end, start_point, end_point, error, min_depth, depth): + """Recursively approximates the length by straight lines""" + mid = (start + end) / 2 + mid_point = curve.point(mid) + length = abs(end_point - start_point) + first_half = abs(mid_point - start_point) + second_half = abs(end_point - mid_point) - @staticmethod - def zeros(rows, cols): - return Matrix([[0 for _ in range(cols)] for _ in range(rows)]) + length2 = first_half + second_half + if (length2 - length > error) or (depth < min_depth): + # Calculate the length of each segment: + depth += 1 + return segment_length( + curve, start, mid, start_point, mid_point, error, min_depth, depth + ) + segment_length( + curve, mid, end, mid_point, end_point, error, min_depth, depth + ) + # This is accurate enough. + return length2 - @staticmethod - def participation(self): - pass - def prod(self, axis=None): - if axis is None: - return reduce(lambda x, y: x * y, [item for sublist in self for item in sublist], 1) - elif axis == 0: - return [reduce(lambda x, y: x * y, [self[row][col] for row in range(len(self))], 1) for col in range(len(self[0]))] - elif axis == 1: - return [reduce(lambda x, y: x * y, row, 1) for row in self] - else: - raise ValueError("Invalid axis.") +class PathSegment(ABC): + @abstractmethod + def point(self, pos): + """Returns the coordinate point (as a complex number) of a point on the path, + as expressed as a floating point number between 0 (start) and 1 (end). + """ - def ptp(self, axis=None): - if axis is None: - flat_list = [item for sublist in self for item in sublist] - return max(flat_list) - min(flat_list) - elif axis == 0: - return [max([self[row][col] for row in range(len(self))]) - min([self[row][col] for row in range(len(self))]) for col in range(len(self[0]))] - elif axis == 1: - return [max(row) - min(row) for row in self] - else: - raise ValueError("Invalid axis.") + @abstractmethod + def tangent(self, pos): + """Returns a vector (as a complex number) representing the tangent of a point + on the path as expressed as a floating point number between 0 (start) and 1 (end). + """ - def put(self, indices, values): - if len(indices) != len(values): - raise ValueError("Length of indices and values must match.") - flat_list = self.ravel() - for index, value in zip(indices, values): - flat_list[index] = value + @abstractmethod + def length(self, error=ERROR, min_depth=MIN_DEPTH): + """Returns the length of a path. - @staticmethod - def random(rows, cols): - import random - return Matrix([[random.random() for _ in range(cols)] for _ in range(rows)]) + The CubicBezier and Arc lengths are non-exact and iterative and you can select to + either do the calculations until a maximum error has been achieved, or a minimum + number of iterations. + """ - def ravel(self): - return [item for sublist in self for item in sublist] - def repeat(self, repeats, axis=None): - if axis is None: - flat_list = self.ravel() - repeated = [item for item in flat_list for _ in range(repeats)] - return Matrix([repeated]) - elif axis == 0: - repeated_matrix = [ - row for row in self for _ in range(repeats)] - elif axis == 1: - repeated_matrix = [ - [item for item in row for _ in range(repeats)] for row in self] - else: - raise ValueError("Invalid axis.") - return Matrix(repeated_matrix) +class NonLinear(PathSegment): + """A line that is not straight - def reshape(self, rows, cols): - flat_list = self.flatten() - if len(flat_list) != rows * cols: - raise ValueError( - "The total size of the new array must be unchanged.") - reshaped = [flat_list[i * cols:(i + 1) * cols] for i in range(rows)] - return Matrix(reshaped) + The base of Arc, QuadraticBezier and CubicBezier + """ - def resize(self, new_shape): - new_rows, new_cols = new_shape - current_rows, current_cols = len(self), len( - self[0]) if self else 0 - if new_rows < current_rows: - self = self[:new_rows] - else: - for _ in range(new_rows - current_rows): - self.append([0] * current_cols) - for row in self: - if new_cols < current_cols: - row[:] = row[:new_cols] - else: - row.extend([0] * (new_cols - current_cols)) - def round(self, decimals=0): - rounded_matrix = [[round(item, decimals) - for item in row] for row in self] - return Matrix(rounded_matrix) +class Linear(PathSegment): + """A straight line - def searchsorted(self, v, side='left'): - flat_list = self.flatten() - i = 0 - if side == 'left': - while i < len(flat_list) and flat_list[i] < v: - i += 1 - elif side == 'right': - while i < len(flat_list) and flat_list[i] <= v: - i += 1 - else: - raise ValueError("side must be 'left' or 'right'") - return i + The base for Line() and Close(). + """ - def setfield(self, val, dtype, offset=0): - raise NotImplementedError( - "Structured data operations are not supported in this Matrix class.") + def __init__(self, start, end, relative=False): + self.start = start + self.end = end + self.relative = relative - def setflags(self, write=None, align=None, uic=None): - print("This Matrix class does not support setting flags directly.") + def __ne__(self, other): + if not isinstance(other, Line): + return NotImplemented + return not self == other - def shape(self): - return len(self), len(self[0]) + def point(self, pos): + distance = self.end - self.start + return self.start + distance * pos - def sort(self, axis=-1): - if axis == -1 or axis == 1: - for row in self: - row.sort() - elif axis == 0: - transposed = [[self[j][i] for j in range( - len(self))] for i in range(len(self[0]))] - for row in transposed: - row.sort() - self = [[transposed[j][i] for j in range( - len(transposed))] for i in range(len(transposed[0]))] - else: - raise ValueError("Axis out of range.") + def tangent(self, pos): + return self.end - self.start - def squeeze(self): - squeezed_matrix = [row for row in self if any(row)] - return Matrix(squeezed_matrix) + def length(self, error=None, min_depth=None): + distance = self.end - self.start + return sqrt(distance.real**2 + distance.imag**2) - def std(self, axis=None, ddof=0): - var = self.var(axis=axis, ddof=ddof) - if isinstance(var, list): - return [x ** 0.5 for x in var] - else: - return var ** 0.5 - def subtract(self, other): - if self.shape() != other.shape(): - raise ValueError("Matrices must have the same dimensions") - return Matrix([[self[i][j] - other.matrix[i][j] for j in range(len(self[0]))] for i in range(len(self))]) +class Line(Linear): + def __init__(self, start, end, relative=False, vertical=False, horizontal=False): + self.start = start + self.end = end + self.relative = relative + self.vertical = vertical + self.horizontal = horizontal - def sum(self, axis=None): - if axis is None: - return sum(sum(row) for row in self) - elif axis == 0: - return [sum(self[row][col] for row in range(len(self))) for col in range(len(self[0]))] - elif axis == 1: - return [sum(row) for row in self] - else: - raise ValueError("Axis must be None, 0, or 1") + def __repr__(self): + return f"Line(start={self.start}, end={self.end})" - def swapaxes(self, axis1, axis2): - if axis1 == 0 and axis2 == 1 or axis1 == 1 and axis2 == 0: - return Matrix([[self[j][i] for j in range(len(self))] for i in range(len(self[0]))]) - else: - raise ValueError("Axis values out of range for a 2D matrix.") + def __eq__(self, other): + if not isinstance(other, Line): + return NotImplemented + return self.start == other.start and self.end == other.end - def take(self, indices, axis=None): - if axis is None: - flat_list = [item for sublist in self for item in sublist] - return Matrix([flat_list[i] for i in indices]) - elif axis == 0: - return Matrix([self[i] for i in indices]) - else: - raise ValueError( - "Axis not supported or out of range for a 2D matrix.") + def _d(self, previous): + x = self.end.real + y = self.end.imag + if self.relative: + x -= previous.end.real + y -= previous.end.imag - def tobytes(self): - byte_array = bytearray() - for row in self: - for item in row: - byte_array.extend(struct.pack('i', item)) - return bytes(byte_array) + if self.horizontal and self.is_horizontal_from(previous): + cmd = "h" if self.relative else "H" + return f"{cmd} {x:G},{y:G}" - def tofile(self, fid, sep="", format="%s"): - if isinstance(fid, str): - with open(fid, 'wb' if sep == "" else 'w') as f: - self._write_to_file(f, sep, format) - else: - self._write_to_file(fid, sep, format) + if self.vertical and self.is_vertical_from(previous): + cmd = "v" if self.relative else "V" + return f"{cmd} {y:G}" - def _write_to_file(self, file, sep, format): - if sep == "": - file.write(self.tobytes()) - else: - for row in self: - line = sep.join(format % item for item in row) + "\n" - file.write(line) - - def __str__(self): - # '\n'.join([str(row) for row in self]) - #vs code doesn't work with new lines - return 'Matrix(' + list.__str__(self) + ')' + cmd = "l" if self.relative else "L" + return f"{cmd} {x:G},{y:G}" - def trace(self, offset=0): - rows, cols = len(self), len(self[0]) - return sum(self[i][i + offset] for i in range(min(rows, cols - offset)) if 0 <= i + offset < cols) + def is_vertical_from(self, previous): + return self.start == previous.end and self.start.real == self.end.real - def transpose(self): - transposed = [[self[j][i] for j in range( - len(self))] for i in range(len(self[0]))] - return Matrix(transposed) + def is_horizontal_from(self, previous): + return self.start == previous.end and self.start.imag == self.end.imag - def var(self, axis=None, ddof=0): - if axis is None: - flat_list = self.flatten() - mean = sum(flat_list) / len(flat_list) - return sum((x - mean) ** 2 for x in flat_list) / (len(flat_list) - ddof) - elif axis == 0 or axis == 1: - means = self.mean(axis=axis) - if axis == 0: - return [sum((self[row][col] - means[col]) ** 2 for row in range(len(self))) / (len(self) - ddof) for col in range(len(self[0]))] - else: - return [sum((row[col] - means[idx]) ** 2 for col in range(len(row))) / (len(row) - ddof) for idx, row in enumerate(self)] - else: - raise ValueError("Axis must be None, 0, or 1") - def _validate(self): - rows = len(self) - cols = len(self[0]) if rows > 0 else 0 - return rows, cols +class CubicBezier(NonLinear): + def __init__(self, start, control1, control2, end, relative=False, smooth=False): + self.start = start + self.control1 = control1 + self.control2 = control2 + self.end = end + self.relative = relative + self.smooth = smooth -class Intersect: - def __init__(self): - pass + def __repr__(self): + return ( + f"CubicBezier(start={self.start}, control1={self.control1}, " + f"control2={self.control2}, end={self.end}, smooth={self.smooth})" + ) - def get_line_intersect(line1: Line, line2: Line) -> Point: - p1, p2 = line1.start, line1.end - p1X, p1Y, P1Z = p1.x, p1.y, p1.z - p2X, p2Y, P2Z = p2.x, p2.y, p2.z + def __eq__(self, other): + if not isinstance(other, CubicBezier): + return NotImplemented + return ( + self.start == other.start + and self.end == other.end + and self.control1 == other.control1 + and self.control2 == other.control2 + ) - p3, p4 = line2.start, line2.end - p3X, p3Y, P3Z = p3.x, p3.y, p3.z - p4X, p4Y, P4Z = p4.x, p4.y, p4.z + def __ne__(self, other): + if not isinstance(other, CubicBezier): + return NotImplemented + return not self == other - print(p1X, p1Y, P1Z) + def _d(self, previous): + c1 = self.control1 + c2 = self.control2 + end = self.end + if self.relative and previous: + c1 -= previous.end + c2 -= previous.end + end -= previous.end -class Text: - """The `Text` class is designed to represent and manipulate text within a coordinate system, allowing for the creation of text objects with specific fonts, sizes, and positions. It is capable of generating and translating text into a series of geometric representations.""" - def __init__(self, text: str = None, font_family: 'str' = None, cs='CoordinateSystem', height=None) -> "Text": - """Initializes a new Text instance - - - `id` (str): A unique identifier for the text object. - - `type` (str): The class name, "Text". - - `text` (str, optional): The text string to be represented. - - `font_family` (str, optional): The font family of the text, defaulting to "Arial". - - `xyz` (Vector): The origin point of the text in the coordinate system. - - `csglobal` (CoordinateSystem): The global coordinate system applied to the text. - - `x`, `y`, `z` (float): The position offsets for the text within its coordinate system. - - `scale` (float, optional): The scale factor applied to the text size. - - `height` (float, optional): The height of the text characters. - - `bbHeight` (float, optional): The bounding box height of the text. - - `width` (float, optional): The calculated width of the text string. - - `character_offset` (int): The offset between characters. - - `space` (int): The space between words. - - `curves` (list): A list of curves representing the text geometry. - - `points` (list): A list of points derived from the text geometry. - - `path_list` (list): A list containing the path data for each character. - """ - self.id = generateID() - self.text = text - self.font_family = font_family or "arial" - self.xyz = cs.Origin - self.csglobal = cs - self.x, self.y, self.z = 0, 0, 0 - self.scale = None - self.height = height or project.font_height - self.bbHeight = None - self.width = None - self.character_offset = 150 - self.space = 850 - self.curves = [] - self.points = [] - self.path_list = self.load_path() - self.load_o_example = self.load_o() + if self.smooth and self.is_smooth_from(previous): + cmd = "s" if self.relative else "S" + return f"{cmd} {c2.real:G},{c2.imag:G} {end.real:G},{end.imag:G}" - def serialize(self) -> 'dict': - """Serializes the text object's attributes into a dictionary. - This method is useful for exporting the text object's properties, making it easier to save or transmit as JSON. + cmd = "c" if self.relative else "C" + return f"{cmd} {c1.real:G},{c1.imag:G} {c2.real:G},{c2.imag:G} {end.real:G},{end.imag:G}" - #### Returns: - dict: A dictionary containing the serialized attributes of the text object. - - #### Example usage: - ```python + def is_smooth_from(self, previous): + """Checks if this segment would be a smooth segment following the previous""" + if isinstance(previous, CubicBezier): + return self.start == previous.end and (self.control1 - self.start) == ( + previous.end - previous.control2 + ) + else: + return self.control1 == self.start - ``` - """ - id_value = str(self.id) if not isinstance( - self.id, (str, int, float)) else self.id - return { - 'id': id_value, - 'type': self.type, - 'text': self.text, - 'font_family': self.font_family, - 'xyz': self.xyz, - 'csglobal': self.csglobal.serialize(), - 'x': self.x, - 'y': self.y, - 'z': self.z, - 'scale': self.scale, - 'height': self.height, - 'bbHeight': self.bbHeight, - 'width': self.width, - 'character_offset': self.character_offset, - 'space': self.space, - 'curves': [curve.serialize() for curve in self.curves], - 'points': self.points, - 'path_list': self.path_list, - } + def set_smooth_from(self, previous): + assert isinstance(previous, CubicBezier) + self.start = previous.end + self.control1 = previous.end - previous.control2 + self.start + self.smooth = True - def load_path(self) -> 'str': - """Loads the glyph paths for the specified text from a JSON file. - This method fetches the glyph paths for each character in the text attribute, using a predefined font JSON file. + def point(self, pos): + """Calculate the x,y position at a certain position of the path""" + return ( + ((1 - pos) ** 3 * self.start) + + (3 * (1 - pos) ** 2 * pos * self.control1) + + (3 * (1 - pos) * pos**2 * self.control2) + + (pos**3 * self.end) + ) - #### Returns: - str: A string representation of the glyph paths for the text. - - #### Example usage: - ```python + def tangent(self, pos): + return ( + -3 * (1 - pos) ** 2 * self.start + + 3 * (1 - pos) ** 2 * self.control1 + - 6 * pos * (1 - pos) * self.control1 + - 3 * pos**2 * self.control2 + + 6 * pos * (1 - pos) * self.control2 + + 3 * pos**2 * self.end + ) - ``` - """ - with open('library/text/json/Calibri.json', 'r', encoding='utf-8') as file: - response = file.read() - glyph_data = json.loads(response) - output = [] - for letter in self.text: - if letter in glyph_data: - output.append(glyph_data[letter]["glyph-path"]) - elif letter == " ": - output.append("space") - return output + def length(self, error=ERROR, min_depth=MIN_DEPTH): + """Calculate the length of the path up to a certain position""" + start_point = self.point(0) + end_point = self.point(1) + return segment_length(self, 0, 1, start_point, end_point, error, min_depth, 0) - def load_o(self) -> 'str': - """Loads the glyph paths for the specified text from a JSON file. - This method fetches the glyph paths for each character in the text attribute, using a predefined font JSON file. - #### Returns: - str: A string representation of the glyph paths for the text. - - #### Example usage: - ```python +class QuadraticBezier(NonLinear): + def __init__(self, start, control, end, relative=False, smooth=False): + self.start = start + self.end = end + self.control = control + self.relative = relative + self.smooth = smooth - ``` - """ - with open('library/text/json/Calibri.json', 'r', encoding='utf-8') as file: - response = file.read() - glyph_data = json.loads(response) - load_o = [] - letter = "o" - if letter in glyph_data: - load_o.append(glyph_data[letter]["glyph-path"]) - return load_o + def __repr__(self): + return ( + f"QuadraticBezier(start={self.start}, control={self.control}, " + f"end={self.end}, smooth={self.smooth})" + ) - def write(self) -> 'List[List[PolyCurve]]': - """Generates a list of PolyCurve objects representing the text. - Transforms the text into geometric representations based on the specified font, scale, and position. + def __eq__(self, other): + if not isinstance(other, QuadraticBezier): + return NotImplemented + return ( + self.start == other.start + and self.end == other.end + and self.control == other.control + ) - #### Returns: - List[List[PolyCurve]]: A list of lists containing PolyCurve objects representing the text geometry. - - #### Example usage: - ```python + def __ne__(self, other): + if not isinstance(other, QuadraticBezier): + return NotImplemented + return not self == other - ``` - """ - # start ref_symbol - path = self.load_o_example - ref_points = [] - ref_allPoints = [] - for segment in path: - pathx = parse_path(segment) - for segment in pathx: - segment_type = segment.__class__.__name__ - if segment_type == 'Line': - ref_points.extend( - [(segment.start.real, segment.start.imag), (segment.end.real, segment.end.imag)]) - ref_allPoints.extend( - [(segment.start.real, segment.start.imag), (segment.end.real, segment.end.imag)]) - elif segment_type == 'CubicBezier': - ref_points.extend(segment.sample(10)) - ref_allPoints.extend(segment.sample(10)) - elif segment_type == 'QuadraticBezier': - for i in range(11): - t = i / 10.0 - point = segment.point(t) - ref_points.append((point.real, point.imag)) - ref_allPoints.append((point.real, point.imag)) - elif segment_type == 'Arc': - ref_points.extend(segment.sample(10)) - ref_allPoints.extend(segment.sample(10)) - height = self.calculate_bounding_box(ref_allPoints)[2] - self.scale = self.height / height - # end ref_symbol + def _d(self, previous): + control = self.control + end = self.end + if self.relative and previous: + control -= previous.end + end -= previous.end - output_list = [] - for letter_path in self.path_list: - points = [] - allPoints = [] - if letter_path == "space": - self.x += self.space + self.character_offset - pass - else: - path = parse_path(letter_path) - for segment in path: - segment_type = segment.__class__.__name__ - if segment_type == 'Move': - if len(points) > 0: - points = [] - allPoints.append("M") - subpath_started = True - elif subpath_started: - if segment_type == 'Line': - points.extend( - [(segment.start.real, segment.start.imag), (segment.end.real, segment.end.imag)]) - allPoints.extend( - [(segment.start.real, segment.start.imag), (segment.end.real, segment.end.imag)]) - elif segment_type == 'CubicBezier': - points.extend(segment.sample(10)) - allPoints.extend(segment.sample(10)) - elif segment_type == 'QuadraticBezier': - for i in range(11): - t = i / 10.0 - point = segment.point(t) - points.append((point.real, point.imag)) - allPoints.append((point.real, point.imag)) - elif segment_type == 'Arc': - points.extend(segment.sample(10)) - allPoints.extend(segment.sample(10)) - if points: - output_list.append( - self.convert_points_to_polyline(allPoints)) - width = self.calculate_bounding_box(allPoints)[1] - self.x += width + self.character_offset + if self.smooth and self.is_smooth_from(previous): + cmd = "t" if self.relative else "T" + return f"{cmd} {end.real:G},{end.imag:G}" - height = self.calculate_bounding_box(allPoints)[2] - self.bbHeight = height - pList = [] - for ply in flatten(output_list): - translated = self.translate(ply) - pList.append(translated) + cmd = "q" if self.relative else "Q" + return f"{cmd} {control.real:G},{control.imag:G} {end.real:G},{end.imag:G}" - for pl in pList: - for pt in pl.points: - self.points.append(pt) + def is_smooth_from(self, previous): + """Checks if this segment would be a smooth segment following the previous""" + if isinstance(previous, QuadraticBezier): + return self.start == previous.end and (self.control - self.start) == ( + previous.end - previous.control + ) + else: + return self.control == self.start - # print(f'Object text naar objects gestuurd.') - return pList + def set_smooth_from(self, previous): + assert isinstance(previous, QuadraticBezier) + self.start = previous.end + self.control = previous.end - previous.control + self.start + self.smooth = True - def translate(self, polyCurve: 'PolyCurve') -> 'PolyCurve': - """Translates a PolyCurve according to the text object's global coordinate system and scale. + def point(self, pos): + return ( + (1 - pos) ** 2 * self.start + + 2 * (1 - pos) * pos * self.control + + pos**2 * self.end + ) - #### Parameters: - polyCurve (PolyCurve): The PolyCurve to be translated. + def tangent(self, pos): + return ( + self.start * (2 * pos - 2) + + (2 * self.end - 4 * self.control) * pos + + 2 * self.control + ) - #### Returns: - PolyCurve: The translated PolyCurve. - - #### Example usage: - ```python + def length(self, error=None, min_depth=None): + a = self.start - 2 * self.control + self.end + b = 2 * (self.control - self.start) - ``` - """ - trans = [] - for pt in polyCurve.points: - pscale = Point.product(self.scale, pt) - pNew = transform_point_2(pscale, self.csglobal) - trans.append(pNew) - return polyCurve.by_points(trans) + try: + # For an explanation of this case, see + # http://www.malczak.info/blog/quadratic-bezier-curve-length/ + A = 4 * (a.real**2 + a.imag**2) + B = 4 * (a.real * b.real + a.imag * b.imag) + C = b.real**2 + b.imag**2 - def calculate_bounding_box(self, points: 'list[Point]') -> tuple: - """Calculates the bounding box for a given set of points. + Sabc = 2 * sqrt(A + B + C) + A2 = sqrt(A) + A32 = 2 * A * A2 + C2 = 2 * sqrt(C) + BA = B / A2 - #### Parameters: - points (list): A list of points to calculate the bounding box for. + s = ( + A32 * Sabc + + A2 * B * (Sabc - C2) + + (4 * C * A - B**2) * log((2 * A2 + BA + Sabc) / (BA + C2)) + ) / (4 * A32) + except (ZeroDivisionError, ValueError): + if abs(a) < 1e-10: + s = abs(b) + else: + k = abs(b) / abs(a) + if k >= 2: + s = abs(b) - abs(a) + else: + s = abs(a) * (k**2 / 2 - k + 1) + return s - #### Returns: - tuple: A tuple containing the bounding box, its width, and its height. - - #### Example usage: - ```python - ``` - """ +class Arc(NonLinear): + def __init__(self, start, radius, rotation, arc, sweep, end, relative=False): + """radius is complex, rotation is in degrees, + large and sweep are 1 or 0 (True/False also work)""" - points = [elem for elem in points if elem != 'M'] - ptList = [Point2D(pt[0], pt[1]) for pt in points] - bounding_box_polyline = Rect().by_points(ptList) - return bounding_box_polyline, bounding_box_polyline.width, bounding_box_polyline.length + self.start = start + self.radius = radius + self.rotation = rotation + self.arc = bool(arc) + self.sweep = bool(sweep) + self.end = end + self.relative = relative - def convert_points_to_polyline(self, points: 'list[Point]') -> 'PolyCurve': - """Converts a list of points into a PolyCurve. - This method is used to generate a PolyCurve from a series of points, typically derived from text path data. + self._parameterize() - #### Parameters: - points (list): A list of points to be converted into a PolyCurve. + def __repr__(self): + return ( + f"Arc(start={self.start}, radius={self.radius}, rotation={self.rotation}, " + f"arc={self.arc}, sweep={self.sweep}, end={self.end})" + ) - #### Returns: - PolyCurve: A PolyCurve object representing the points. - - #### Example usage: - ```python + def __eq__(self, other): + if not isinstance(other, Arc): + return NotImplemented + return ( + self.start == other.start + and self.end == other.end + and self.radius == other.radius + and self.rotation == other.rotation + and self.arc == other.arc + and self.sweep == other.sweep + ) - ``` - """ - output_list = [] - sub_lists = [[]] - tempPoints = [elem for elem in points if elem != 'M'] - x_values = [point[0] for point in tempPoints] - y_values = [point[1] for point in tempPoints] + def __ne__(self, other): + if not isinstance(other, Arc): + return NotImplemented + return not self == other - xmin = min(x_values) - ymin = min(y_values) + def _d(self, previous): + end = self.end + cmd = "a" if self.relative else "A" + if self.relative: + end -= previous.end - for item in points: - if item == 'M': - sub_lists.append([]) - else: - x = item[0] + self.x - xmin - y = item[1] + self.y - ymin - z = self.xyz.z - eput = x, y, z - sub_lists[-1].append(eput) - output_list = [[Point(point[0], point[1], self.xyz.z) - for point in element] for element in sub_lists] + return ( + f"{cmd} {self.radius.real:G},{self.radius.imag:G} {self.rotation:G} " + f"{int(self.arc):d},{int(self.sweep):d} {end.real:G},{end.imag:G}" + ) - polyline_list = [ - PolyCurve.by_points( - [Point(coord.x, coord.y, self.xyz.z) for coord in pts]) - for pts in output_list - ] - return polyline_list + def _parameterize(self): + # Conversion from endpoint to center parameterization + # http://www.w3.org/TR/SVG/implnote.html#ArcImplementationNotes + if self.start == self.end: + # This is equivalent of omitting the segment, so do nothing + return + if self.radius.real == 0 or self.radius.imag == 0: + # This should be treated as a straight line + return -class Geometry: - def Translate(object, v): - if object.type == 'Point': - p1 = Point.to_matrix(object) - v1 = Vector.to_matrix(v) + cosr = cos(radians(self.rotation)) + sinr = sin(radians(self.rotation)) + dx = (self.start.real - self.end.real) / 2 + dy = (self.start.imag - self.end.imag) / 2 + x1prim = cosr * dx + sinr * dy + x1prim_sq = x1prim * x1prim + y1prim = -sinr * dx + cosr * dy + y1prim_sq = y1prim * y1prim - ar1 = Point.to_matrix(p1) - ar2 = Vector.to_matrix(v1) + rx = self.radius.real + rx_sq = rx * rx + ry = self.radius.imag + ry_sq = ry * ry - c = [ar1[i] + ar2[i] for i in range(len(ar1))] + # Correct out of range radii + radius_scale = (x1prim_sq / rx_sq) + (y1prim_sq / ry_sq) + if radius_scale > 1: + radius_scale = sqrt(radius_scale) + rx *= radius_scale + ry *= radius_scale + rx_sq = rx * rx + ry_sq = ry * ry + self.radius_scale = radius_scale + else: + # SVG spec only scales UP + self.radius_scale = 1 - return Point(c[0], c[1], c[2]) + t1 = rx_sq * y1prim_sq + t2 = ry_sq * x1prim_sq + c = sqrt(abs((rx_sq * ry_sq - t1 - t2) / (t1 + t2))) - elif object.type == 'Line': - return Line(Geometry.Translate(object.start, v), (Geometry.Translate(object.end, v))) + if self.arc == self.sweep: + c = -c + cxprim = c * rx * y1prim / ry + cyprim = -c * ry * x1prim / rx - elif object.type == "PolyCurve": - translated_points = [] + self.center = complex( + (cosr * cxprim - sinr * cyprim) + ((self.start.real + self.end.real) / 2), + (sinr * cxprim + cosr * cyprim) + ((self.start.imag + self.end.imag) / 2), + ) - # Extract the direction components from the Vector object - direction_x, direction_y, direction_z = v.x, v.y, v.z + ux = (x1prim - cxprim) / rx + uy = (y1prim - cyprim) / ry + vx = (-x1prim - cxprim) / rx + vy = (-y1prim - cyprim) / ry + n = sqrt(ux * ux + uy * uy) + p = ux + theta = degrees(acos(p / n)) + if uy < 0: + theta = -theta + self.theta = theta % 360 - for point in object.points: - p1 = Point.to_matrix(point) - # Apply the translation - c = [p1[0] + direction_x, p1[1] + - direction_y, p1[2] + direction_z] + n = sqrt((ux * ux + uy * uy) * (vx * vx + vy * vy)) + p = ux * vx + uy * vy + d = p / n + # In certain cases the above calculation can through inaccuracies + # become just slightly out of range, f ex -1.0000000000000002. + if d > 1.0: + d = 1.0 + elif d < -1.0: + d = -1.0 + delta = degrees(acos(d)) + if (ux * vy - uy * vx) < 0: + delta = -delta + self.delta = delta % 360 + if not self.sweep: + self.delta -= 360 - translated_points.append(Point(c[0], c[1], c[2])) + def point(self, pos): + if self.start == self.end: + # This is equivalent of omitting the segment + return self.start - return PolyCurve.by_points(translated_points) - else: - print(f"[translate] '{object.type}' object is not added yet") + if self.radius.real == 0 or self.radius.imag == 0: + # This should be treated as a straight line + distance = self.end - self.start + return self.start + distance * pos + angle = radians(self.theta + (self.delta * pos)) + cosr = cos(radians(self.rotation)) + sinr = sin(radians(self.rotation)) + radius = self.radius * self.radius_scale -class TickMark: - # Dimension Tick Mark - def __init__(self): - self.name = None - self.id = generateID() - self.curves = [] + x = ( + cosr * cos(angle) * radius.real + - sinr * sin(angle) * radius.imag + + self.center.real + ) + y = ( + sinr * cos(angle) * radius.real + + cosr * sin(angle) * radius.imag + + self.center.imag + ) + return complex(x, y) - @staticmethod - def by_curves(name, curves): - TM = TickMark() - TM.name = name - TM.curves = curves - return TM + def tangent(self, pos): + angle = radians(self.theta + (self.delta * pos)) + cosr = cos(radians(self.rotation)) + sinr = sin(radians(self.rotation)) + radius = self.radius * self.radius_scale + x = cosr * cos(angle) * radius.real - sinr * sin(angle) * radius.imag + y = sinr * cos(angle) * radius.real + cosr * sin(angle) * radius.imag + return complex(x, y) * complex(0, 1) -TMDiagonal = TickMark.by_curves( - "diagonal", [Line(start=Point(-100, -100, 0), end=Point(100, 100, 0))]) + def length(self, error=ERROR, min_depth=MIN_DEPTH): + """The length of an elliptical arc segment requires numerical + integration, and in that case it's simpler to just do a geometric + approximation, as for cubic bezier curves. + """ + if self.start == self.end: + # This is equivalent of omitting the segment + return 0 + if self.radius.real == 0 or self.radius.imag == 0: + # This should be treated as a straight line + distance = self.end - self.start + return sqrt(distance.real**2 + distance.imag**2) -class DimensionType: - def __init__(self): - self.name = None - self.id = generateID() - self.font = None - self.text_height = 2.5 - self.tick_mark: TickMark = TMDiagonal - self.line_extension = 100 + if self.radius.real == self.radius.imag: + # It's a circle, which simplifies this a LOT. + radius = self.radius.real * self.radius_scale + return abs(radius * self.delta * pi / 180) - def serialize(self): - return { - 'name': self.name, - 'id': self.id, - 'type': self.type, - 'font': self.font, - 'text_height': self.text_height, - 'tick_mark': str(self.tick_mark), - 'line_extension': self.line_extension - } + start_point = self.point(0) + end_point = self.point(1) + return segment_length(self, 0, 1, start_point, end_point, error, min_depth, 0) - @staticmethod - def deserialize(data): - dimension_type = DimensionType() - dimension_type.name = data.get('name') - dimension_type.id = data.get('id') - dimension_type.type = data.get('type') - dimension_type.font = data.get('font') - dimension_type.text_height = data.get('text_height', 2.5) - # Handle TickMark deserialization - tick_mark_str = data.get('tick_mark') - # Adjust according to your TickMark implementation - dimension_type.tick_mark = TickMark(tick_mark_str) +class Move: + """Represents move commands. Does nothing, but is there to handle + paths that consist of only move commands, which is valid, but pointless. + """ - dimension_type.line_extension = data.get('line_extension', 100) + def __init__(self, to, relative=False): + self.start = self.end = to + self.relative = relative - return dimension_type + def __repr__(self): + return "Move(to=%s)" % self.start - @staticmethod - def by_name_font_textheight_tick_mark_extension(name: str, font: str, text_height: float, tick_mark: TickMark, line_extension: float): - DT = DimensionType() - DT.name = name - DT.font = font - DT.text_height = text_height - DT.tick_mark = tick_mark - DT.line_extension = line_extension - return DT + def __eq__(self, other): + if not isinstance(other, Move): + return NotImplemented + return self.start == other.start + def __ne__(self, other): + if not isinstance(other, Move): + return NotImplemented + return not self == other -DT2_5_mm = DimensionType.by_name_font_textheight_tick_mark_extension( - "2.5 mm", "calibri", 2.5, TMDiagonal, 100) + def _d(self, previous): + cmd = "M" + x = self.end.real + y = self.end.imag + if self.relative: + cmd = "m" + if previous: + x -= previous.end.real + y -= previous.end.imag + return f"{cmd} {x:G},{y:G}" -DT1_8_mm = DimensionType.by_name_font_textheight_tick_mark_extension( - "1.8 mm", "calibri", 2.5, TMDiagonal, 100) + def point(self, pos): + return self.start + def tangent(self, pos): + return 0 -class Dimension: - def __init__(self, start: Point, end: Point, dimension_type) -> None: - self.id = generateID() - self.start: Point = start - self.text_height = 100 - self.end: Point = end - self.scale = 0.1 # text - self.dimension_type: DimensionType = dimension_type - self.curves = [] - self.length: float = Line(start=self.start, end=self.end).length - self.text = None - self.geom() + def length(self, error=ERROR, min_depth=MIN_DEPTH): + return 0 - def serialize(self): - return { - 'type': self.type, - 'start': self.start.serialize(), - 'end': self.end.serialize(), - 'text_height': self.text_height, - 'id': self.id, - 'scale': self.scale, - 'dimension_type': self.dimension_type.serialize(), - 'curves': [curve.serialize() for curve in self.curves], - 'length': self.length, - 'text': self.text - } - @staticmethod - def deserialize(data): - start = Point.deserialize(data['start']) - end = Point.deserialize(data['end']) - dimension_type = DimensionType.deserialize(data['dimension_type']) - dimension = Dimension(start, end, dimension_type) +class Close(Linear): + """Represents the closepath command""" - dimension.text_height = data.get('text_height', 100) - dimension.id = data.get('id') - dimension.scale = data.get('scale', 0.1) - dimension.curves = [Line.deserialize( - curve_data) for curve_data in data.get('curves', [])] - dimension.length = data.get('length') - dimension.text = data.get('text') + def __eq__(self, other): + if not isinstance(other, Close): + return NotImplemented + return self.start == other.start and self.end == other.end - return dimension + def __repr__(self): + return f"Close(start={self.start}, end={self.end})" - @staticmethod - def by_startpoint_endpoint_offset(start: Point, end: Point, dimension_type: DimensionType, offset: float): - DS = Dimension() - DS.start = start - DS.end = end - DS.dimension_type = dimension_type - DS.geom() - return DS + def _d(self, previous): + return "z" if self.relative else "Z" - def geom(self): - # baseline - baseline = Line(start=self.start, end=self.end) - midpoint_text = baseline.mid_point() - direction = Vector.normalize(baseline.vector) - tick_mark_extension_point_1 = Point.translate(self.start, Vector.reverse( - Vector.scale(direction, self.dimension_type.line_extension))) - tick_mark_extension_point_2 = Point.translate( - self.end, Vector.scale(direction, self.dimension_type.line_extension)) - x = direction - y = Vector.rotate_XY(x, math.radians(90)) - z = Z_Axis - cs_new_start = CoordinateSystem(self.start, x, y, z) - cs_new_mid = CoordinateSystem(midpoint_text, x, y, z) - cs_new_end = CoordinateSystem(self.end, x, y, z) - self.curves.append(Line(tick_mark_extension_point_1, - self.start)) # extention_start - self.curves.append( - Line(tick_mark_extension_point_2, self.end)) # extention_end - self.curves.append(Line(self.start, self.end)) # baseline - # erg vieze oplossing. #Todo - crvs = Line( - start=self.dimension_type.tick_mark.curves[0].start, end=self.dimension_type.tick_mark.curves[0].end) - self.curves.append(Line.transform( - self.dimension_type.tick_mark.curves[0], cs_new_start)) # dimension tick start - self.curves.append(Line.transform(crvs, cs_new_end) - ) # dimension tick end - self.text = Text(text=str(round(self.length)), font_family=self.dimension_type.font, - cs=cs_new_mid, height=self.text_height).write() +class Path(MutableSequence): + """A Path is a sequence of path segments""" - def write(self, project): - for i in self.curves: - project.objects.append(i) - for j in self.text: - project.objects.append(j) + def __init__(self, *segments): + self._segments = list(segments) + self._length = None + self._lengths = None + # Fractional distance from starting point through the end of each segment. + self._fractions = [] + def __getitem__(self, index): + return self._segments[index] -class FrameTag: - def __init__(self): - # Dimensions in 1/100 scale - self.id = generateID() - self.scale = 0.1 - self.cs: CoordinateSystem = CSGlobal - self.offset_x = 500 - self.offset_y = 100 - self.font_family = "calibri" - self.text: str = "text" - self.text_curves = None - self.text_height = 100 + def __setitem__(self, index, value): + self._segments[index] = value + self._length = None - def serialize(self): - id_value = str(self.id) if not isinstance( - self.id, (str, int, float)) else self.id - return { - 'id': id_value, - 'type': self.type, - 'scale': self.scale, - 'cs': self.cs.serialize(), - 'offset_x': self.offset_x, - 'offset_y': self.offset_y, - 'font_family': self.font_family, - 'text': self.text, - 'text_curves': self.text_curves, - 'text_height': self.text_height - } + def __delitem__(self, index): + del self._segments[index] + self._length = None - @staticmethod - def deserialize(data): - frame_tag = FrameTag() - frame_tag.scale = data.get('scale', 0.1) - frame_tag.cs = CoordinateSystem.deserialize(data['cs']) - frame_tag.offset_x = data.get('offset_x', 500) - frame_tag.offset_y = data.get('offset_y', 100) - frame_tag.font_family = data.get('font_family', "calibri") - frame_tag.text = data.get('text', "text") - frame_tag.text_curves = data.get('text_curves') - frame_tag.text_height = data.get('text_height', 100) + def insert(self, index, value): + self._segments.insert(index, value) + self._length = None - return frame_tag + def reverse(self): + # Reversing the order of a path would require reversing each element + # as well. That's not implemented. + raise NotImplementedError - def __textobject(self): - cstext = self.cs - # cstextnew = cstext.translate(self.textoff_vector_local) - self.text_curves = Text( - text=self.text, font_family=self.font_family, height=self.text_height, cs=cstext).write + def __len__(self): + return len(self._segments) - def by_cs_text(self, coordinate_system: CoordinateSystem, text): - self.cs = coordinate_system - self.text = text - self.__textobject() - return self + def __repr__(self): + return "Path(%s)" % (", ".join(repr(x) for x in self._segments)) - def write(self, project): - for x in self.text_curves(): - project.objects.append(x) - return self + def __eq__(self, other): - @staticmethod - def by_frame(frame): - tag = FrameTag() - frame_vector = frame.vector_normalised - x = frame_vector - y = Vector.rotate_XY(x, math.radians(90)) - z = Z_Axis - vx = Vector.scale(frame_vector, tag.offset_x) - frame_width = PolyCurve2D.bounds(frame.curve)[4] - vy = Vector.scale(y, frame_width*0.5+tag.offset_y) - origintext = Point.translate(frame.start, vx) - origintext = Point.translate(origintext, vy) - csnew = CoordinateSystem(origintext, x, y, z) - tag.cs = csnew - tag.text = frame.name - tag.__textobject() - return tag + if not isinstance(other, Path): + return NotImplemented + if len(self) != len(other): + return False + for s, o in zip(self._segments, other._segments): + if not s == o: + return False + return True + def __ne__(self, other): + if not isinstance(other, Path): + return NotImplemented + return not self == other -class ColumnTag: - def __init__(self): - # Dimensions in 1/100 scale - self.id = generateID() - self.width = 700 - self.height = 500 - self.factor = 3 # hellingsfacor leader - self.scale = 0.1 # voor tekeningverschaling - self.position = "TL" # TL, TR, BL, BR Top Left Top Right Bottom Left Bottom Right - self.cs: CoordinateSystem = CSGlobal + def _calc_lengths(self, error=ERROR, min_depth=MIN_DEPTH): + if self._length is not None: + return - # self.textoff_vector_local: Vector = Vector(1,1,1) - self.font_family = "calibri" - self.curves = [] - # self.leadercurves() - self.text: str = "text" - self.text_height = 100 - self.text_offset_factor = 5 - self.textoff_vector_local: Vector = Vector( - self.height/self.factor, self.height+self.height/self.text_offset_factor, 0) - self.text_curves = None - # self.textobject() + lengths = [ + each.length(error=error, min_depth=min_depth) for each in self._segments + ] + self._length = sum(lengths) + if self._length == 0: + self._lengths = lengths + else: + self._lengths = [each / self._length for each in lengths] + # Calculate the fractional distance for each segment to use in point() + fraction = 0 + for each in self._lengths: + fraction += each + self._fractions.append(fraction) - def serialize(self): - id_value = str(self.id) if not isinstance( - self.id, (str, int, float)) else self.id - return { - 'id': id_value, - 'type': self.type, - 'width': self.width, - 'height': self.height, - 'factor': self.factor, - 'scale': self.scale, - 'position': self.position, - 'cs': self.cs.serialize(), - 'font_family': self.font_family, - 'curves': [curve.serialize() for curve in self.curves], - 'text': self.text, - 'text_height': self.text_height, - 'text_offset_factor': self.text_offset_factor, - 'textoff_vector_local': self.textoff_vector_local.serialize(), - 'text_curves': self.text_curves - } + def _find_segment(self, pos, error=ERROR): + # Shortcuts + if pos == 0.0: + return self._segments[0], pos + if pos == 1.0: + return self._segments[-1], pos - @staticmethod - def deserialize(data): - column_tag = ColumnTag() - column_tag.width = data.get('width', 700) - column_tag.height = data.get('height', 500) - column_tag.factor = data.get('factor', 3) - column_tag.scale = data.get('scale', 0.1) - column_tag.position = data.get('position', "TL") - column_tag.cs = CoordinateSystem.deserialize(data['cs']) - column_tag.font_family = data.get('font_family', "calibri") - column_tag.curves = [Line.deserialize( - curve_data) for curve_data in data.get('curves', [])] - column_tag.text = data.get('text', "text") - column_tag.text_height = data.get('text_height', 100) - column_tag.text_offset_factor = data.get('text_offset_factor', 5) - column_tag.textoff_vector_local = Vector.deserialize( - data['textoff_vector_local']) - column_tag.text_curves = data.get('text_curves') + self._calc_lengths(error=error) - return column_tag + # Fix for paths of length 0 (i.e. points) + if self._length == 0: + return self._segments[0], 0.0 - def __leadercurves(self): - self.startpoint = Point(0, 0, 0) - self.midpoint = Point.translate(self.startpoint, Vector( - self.height/self.factor, self.height, 0)) - self.endpoint = Point.translate( - self.midpoint, Vector(self.width, 0, 0)) - crves = [Line(start=self.startpoint, end=self.midpoint), - Line(start=self.midpoint, end=self.endpoint)] - for i in crves: - j = Line.transform(i, self.cs) - self.curves.append(j) + # Find which segment the point we search for is located on: + i = bisect(self._fractions, pos) + if i == 0: + segment_pos = pos / self._fractions[0] + else: + segment_pos = (pos - self._fractions[i - 1]) / ( + self._fractions[i] - self._fractions[i - 1] + ) + return self._segments[i], segment_pos - def __textobject(self): - cstext = self.cs + def point(self, pos, error=ERROR): + segment, pos = self._find_segment(pos, error) + return segment.point(pos) - cstextnew = CoordinateSystem.translate( - cstext, self.textoff_vector_local) - self.text_curves = Text(text=self.text, font_family=self.font_family, - height=self.text_height, cs=cstextnew).write + def tangent(self, pos, error=ERROR): + segment, pos = self._find_segment(pos, error) + return segment.tangent(pos) - def by_cs_text(self, coordinate_system: CoordinateSystem, text): - self.cs = coordinate_system - self.text = text - self.__leadercurves() - self.__textobject() - return self + def length(self, error=ERROR, min_depth=MIN_DEPTH): + self._calc_lengths(error, min_depth) + return self._length - def write(self, project): - for x in self.text_curves(): - project.objects.append(x) - for y in self.curves: - project.objects.append(y) + def d(self): + parts = [] + previous_segment = None - @staticmethod - def by_frame(frame, position="TL"): - tag = ColumnTag() - csold = CSGlobal - tag.position = position - tag.cs = CoordinateSystem.translate(csold, Vector( - frame.start.x, frame.start.y, frame.start.z)) - tag.text = frame.name - tag.__leadercurves() - tag.__textobject() - return tag + for segment in self: + parts.append(segment._d(previous_segment)) + previous_segment = segment -# class Label: -# class LabelType: -# class TextType: + return " ".join(parts) seqChar = "A B C D E F G H I J K L M N O P Q R S T U V W X Y Z AA AB AC" seqNumber = "1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24" diff --git a/sandbox/Versie juni 2024/example/GIS2BIM/NL_0_geocoding test.py b/sandbox/Versie juni 2024/example/GIS2BIM/NL_0_geocoding test.py new file mode 100644 index 0000000..89be5c0 --- /dev/null +++ b/sandbox/Versie juni 2024/example/GIS2BIM/NL_0_geocoding test.py @@ -0,0 +1,129 @@ +from packages.GIS2BIM.GIS2BIM_NL import * + +test = NL_GetLocationData(NLPDOKServerURL,"Dordrecht","werf van schouten", "501") + +class NL_Geocoding: + def __init__(self): + self.servername = NLPDOKServerURL + self.bron = None + self.woonplaatscode = None + self.type = None + self.woonplaatsnaam = None + self.wijkcode = None + self.huis_nlt = None + self.openbareruimtetype = None + self.buurtnaam = None + self.gemeentecode = None + self.rdf_seealso = None + self.rdf_seealso = None + self.weergavenaam = None + self.straatnaam_verkort = None + self.id = None + self.gekoppeld_perceel = None + self.gemeentenaam = None + self.buurtcode = None + self.wijknaam = None + self.identificatie = None + self.openbareruimte_id = None + self.waterschapsnaam = None + self.provinciecode = None + self.postcode = None + self.provincienaam = None + self.centroide_ll = None + self.centroid_X = None + self.centroid_Y = None + self.nummeraanduiding_id = None + self.waterschapscode = None + self.adresseerbaarobject_id = None + self.huisnummer = None + self.provincieafkorting = None + self.centroide_rd = None + self.rdx = None + self.rdy = None + self.straatnaam = None + self.score = None + self.url = None + + def by_address(self, City: str, Streetname: str, Housenumber: str): + # Use PDOK location server to get X & Y data + PDOKServer = self.servername + SN = Streetname.replace(" ", "%20") + self.url = PDOKServer + City + "%20and%20" + SN + "%20and%20" + Housenumber + urlFile = urllib.request.urlopen(self.url) + jsonList = json.load(urlFile) + jsonList = jsonList["response"]["docs"] + jsonList1 = jsonList[0] + self.fill_param(jsonList1) + return self + + def by_rdx_rdy(self, rdx: float, rdy: float): + PDOKServer = "https://api.pdok.nl/bzk/locatieserver/search/v3_1/reverse?X=RDX&Y=RDY&rows=1" + PDOKServer = PDOKServer.replace("RDX", str(rdx)) + PDOKServer = PDOKServer.replace("RDY", str(rdy)) + urlFile = urllib.request.urlopen(PDOKServer) + jsonList = json.load(urlFile) + id = jsonList["response"]["docs"][0]["id"] + PDOKServer = self.servername + PDOKServer = PDOKServer + id + urlFile = urllib.request.urlopen(PDOKServer) + jsonList = json.load(urlFile) + jsonList = jsonList["response"]["docs"] + jsonList1 = jsonList[0] + self.fill_param(jsonList1) + self.url = PDOKServer + return self + def fill_param(self, resp): + jsonList1 = resp + RD = jsonList1['centroide_rd'] + RD = RD.replace("(", " ").replace(")", " ") + RD = RD.split() + RDx = float(RD[1]) + RDy = float(RD[2]) + LatLon = jsonList1['centroide_ll'] + LatLon = LatLon.replace("(", " ").replace(")", " ") + LatLon = LatLon.split() + Lat = float(LatLon[1]) + Lon = float(LatLon[2]) + self.bron = jsonList1['bron'] + self.woonplaatscode = jsonList1['woonplaatscode'] + self.type = jsonList1['type'] + self.woonplaatsnaam = jsonList1['woonplaatsnaam'] + self.wijkcode = jsonList1['wijkcode'] + self.huis_nlt = jsonList1['huis_nlt'] + self.openbareruimtetype = jsonList1['openbareruimtetype'] + self.buurtnaam = jsonList1['buurtnaam'] + self.gemeentecode = jsonList1['gemeentecode'] + self.rdf_seealso = jsonList1['rdf_seealso'] + self.weergavenaam = jsonList1['weergavenaam'] + self.straatnaam_verkort = jsonList1['straatnaam_verkort'] + self.id = jsonList1['id'] + self.gekoppeld_perceel = jsonList1['gekoppeld_perceel'] + self.gemeentenaam = jsonList1['gemeentenaam'] + self.buurtcode = jsonList1['buurtcode'] + self.wijknaam = jsonList1['wijknaam'] + self.identificatie = jsonList1['identificatie'] + self.openbareruimte_id = jsonList1['openbareruimte_id'] + self.waterschapsnaam = jsonList1['waterschapsnaam'] + self.provinciecode = jsonList1['provinciecode'] + self.postcode = jsonList1['postcode'] + self.provincienaam = jsonList1['provincienaam'] + self.centroide_ll = jsonList1['centroide_ll'] + self.lat = Lat + self.lon = Lon + self.nummeraanduiding_id = jsonList1['nummeraanduiding_id'] + self.waterschapscode = jsonList1['waterschapscode'] + self.adresseerbaarobject_id = jsonList1['adresseerbaarobject_id'] + self.huisnummer = jsonList1['huisnummer'] + self.provincieafkorting = jsonList1['provincieafkorting'] + self.centroide_rd = jsonList1['centroide_rd'] + self.rdx = RDx + self.rdy = RDy + self.straatnaam = jsonList1['straatnaam'] + self.score = jsonList1['score'] + return self + +geoc = NL_Geocoding().by_address("Dordrecht","werf van schouten", "501") +geoc2 = NL_Geocoding().by_rdx_rdy(194195.304, 465885.902) + +#print(geoc.rdx) +#print(geoc2.url) diff --git a/sandbox/Versie juni 2024/example/GIS2BIM/NL_3_Cadaster.py b/sandbox/Versie juni 2024/example/GIS2BIM/NL_3_Cadaster.py index 16a4b5c..21de249 100644 --- a/sandbox/Versie juni 2024/example/GIS2BIM/NL_3_Cadaster.py +++ b/sandbox/Versie juni 2024/example/GIS2BIM/NL_3_Cadaster.py @@ -1,9 +1,11 @@ from packages.GIS2BIM.GIS2BIM_NL import * +from packages.GIS2BIM.GIS2BIM import * from project.fileformat import * from exchange.GIS2BIM import * from geometry.mesh import MeshPB from library.material import * from abstract.image import * +#from GIS2BIM_single_file import * # Description # This script creates lines for the cadastral parcels and a mesh for a building footprint for any building in @@ -22,11 +24,11 @@ # BASE VALUES RdX = lst[0] RdY = lst[1] -Bbox = GIS2BIM.CreateBoundingBox(RdX, RdY, Bboxwidth, Bboxwidth, 0) +Bbox = CreateBoundingBox(RdX, RdY, Bboxwidth, Bboxwidth, 0) # Aerialphoto fileLocationWMS = tempfolder + "luchtfoto_2020_2.png" -a = GIS2BIM.WMSRequest(GIS2BIM.GetWebServerData("NL_PDOK_Luchtfoto_2020_28992", "webserverRequests", +a = WMSRequest(GetWebServerData("NL_PDOK_Luchtfoto_2020_28992", "webserverRequests", "serverrequestprefix"), Bbox, fileLocationWMS, 1500, 1500) @@ -34,14 +36,14 @@ # GISProject.objects.append(img) # KADASTRALE GRENZEN -curvesCadaster = GIS2BIM.PointsFromWFS(NLPDOKCadastreCadastralParcels, Bbox, NLPDOKxPathOpenGISposList, -RdX, -RdY, +curvesCadaster = PointsFromWFS(NLPDOKCadastreCadastralParcels, Bbox, NLPDOKxPathOpenGISposList, -RdX, -RdY, 1000, 2) # for i in WFSCurvesToBPCurvesLinePattern(curvesCadaster, Centerline): for i in WFSCurvesToBPCurves(curvesCadaster): GISProject.objects.append(i) # GEBOUWEN -curvesBAG = GIS2BIM.PointsFromWFS(NLPDOKBAGBuildingCountour, Bbox, NLPDOKxPathOpenGISposList, -RdX, -RdY, 1000, +curvesBAG = PointsFromWFS(NLPDOKBAGBuildingCountour, Bbox, NLPDOKxPathOpenGISposList, -RdX, -RdY, 1000, 2) BPCurvesBAG = WFSCurvesToBPCurves(curvesBAG) # for i in WFSCurvesToBPCurves(curvesBAG): diff --git a/sandbox/Versie juni 2024/example/GIS2BIM/NL_DownloadBasis.py b/sandbox/Versie juni 2024/example/GIS2BIM/NL_DownloadBasis.py index 4e298d9..e48bd9f 100644 --- a/sandbox/Versie juni 2024/example/GIS2BIM/NL_DownloadBasis.py +++ b/sandbox/Versie juni 2024/example/GIS2BIM/NL_DownloadBasis.py @@ -6,6 +6,25 @@ from packages.GIS2BIM.GIS2BIM import * from project.fileformat import * from packages.GIS2BIM.GIS2BIM_NL_helpers import * + +import ssl + +#SETTINGS BASIS +GISProject = BuildingPy("test") +ProjectDrive = "E:/" +ProjectFolder = ProjectDrive + "GIS2BIM/" +City = "Lekkerkerk" +Street = "Voorstraat" +HouseNumber = "172" +ProjectName = City + "_" + Street + "_" + HouseNumber +lst = NL_GetLocationData(NLPDOKServerURL, City, Street, HouseNumber) +Bboxwidth = 200#m +#BOUNDINGBOX +RdX = lst[0] +RdY = lst[1] +Bbox = GIS2BIM.CreateBoundingBox(RdX,RdY,Bboxwidth,Bboxwidth,0) +print(Bbox) +======= from packages.GIS2BIM.GIS2BIM_CityJSON import * import time import ijson @@ -14,6 +33,7 @@ import certifi import ssl + def CreateFolder(Folder): #check and ccreate folders if not os.path.isdir(Folder): @@ -21,8 +41,13 @@ def CreateFolder(Folder): os.mkdir(Folder) #CITYJSON DOWNLOAD + +def DownloadCityJSON(): + kaartbladenres = kaartbladenBbox(Bbox, FolderCityJSON) +======= def DownloadCityJSON(Bbox, FolderCityJSON): kaartbladenres = kaartbladenBbox(Bbox) + print(kaartbladenres) for i in kaartbladenres: downloadlink = NLPDOKKadasterBasisvoorziening3DCityJSONVolledig + i[0] + "_2020_volledig.zip" @@ -66,6 +91,19 @@ def GetWebServerDataCategorised(obj1, obj2, obj3): jsonData = json.loads(url.read())[obj1][obj2][obj3] return jsonData + +#WMS +jsonobj=GetWebServerDataCategorised("GIS2BIMserversRequests", "NLwebserverRequests", "NLWMS") +serverrequestprefix= extract_values(jsonobj, 'serverrequestprefix') +title = extract_values(jsonobj, 'title') +print("ItemsToDownload: " + str(len(serverrequestprefix))) +print(FolderImages) +for i in range(len(serverrequestprefix)): + itemserverrequestprefix=serverrequestprefix[i] + itemtitle=title[i] + print(itemtitle) + WMSRequestNew(itemserverrequestprefix, Bbox, FolderImages + "/"+ itemtitle +".png", 1500, 1500) +======= def GetWebServerDataSettings(SettingsfileLocation): #get settings url = urllib.request.urlopen(SettingsfileLocation) @@ -229,4 +267,4 @@ def Create3DBag(Folder, RdX,RdY,LOD,Bboxwidth): GISProject.toSpeckle("6b2055857b", "3D Kadaster Basisvoorziening en 3D BAG") end = time.time() -print('Execution time:', end - start, 'seconds') \ No newline at end of file +print('Execution time:', end - start, 'seconds')