def test_basic_arc2(): origin = (0, 0, 0) xdir = (1, 0, 0) normal = (0, -1, 0) p1 = np.array([-150, 100]) p2 = np.array([-74, 81]) p3 = np.array([-20, 0]) radius = 40 v1 = unit_vector(p2 - p1) v2 = unit_vector(p2 - p3) alpha = angle_between(v1, v2) s = radius / np.sin(alpha / 2) dir_eval = np.cross(v1, v2) if dir_eval < 0: theta = -alpha / 2 else: theta = alpha / 2 A = p2 - v1 * s center = linear_2dtransform_rotate(p2, A, np.rad2deg(theta)) start = intersect_line_circle((p1, p2), center, radius) end = intersect_line_circle((p3, p2), center, radius) vc1 = np.array([start[0], start[1], 0.0]) - np.array([center[0], center[1], 0.0]) vc2 = np.array([end[0], end[1], 0.0]) - np.array([center[0], center[1], 0.0]) arbp = angle_between(vc1, vc2) if dir_eval < 0: gamma = arbp / 2 else: gamma = -arbp / 2 midp = linear_2dtransform_rotate(center, start, np.rad2deg(gamma)) glob_c = local_2_global_points([center], origin, xdir, normal)[0] glob_s = local_2_global_points([start], origin, xdir, normal)[0] glob_e = local_2_global_points([end], origin, xdir, normal)[0] glob_midp = local_2_global_points([midp], origin, xdir, normal)[0] res_center = (-98.7039754, 0.0, 45.94493759) res_start = (-89.00255040102925, 0, 84.75063760025732) res_end = (-65.4219636289688, 0, 68.1329454434532) res_midp = (-75.66203793182973, 0, 78.64156001325857) for r, e in zip(res_start, glob_s): assert roundoff(r, 5) == roundoff(e, 5) for r, e in zip(res_end, glob_e): assert roundoff(r, 4) == roundoff(e, 4) for r, e in zip(res_midp, glob_midp): assert roundoff(r, 4) == roundoff(e, 4) for r, e in zip(res_center, glob_c): assert roundoff(r, 4) == roundoff(e, 4)
def extrusion_area(self): from ada.core.vector_utils import calc_yvec, intersect_calc, is_parallel area_points = [] vpo = [np.array(p) for p in self.points] p2 = None yvec = None prev_xvec = None prev_yvec = None zvec = np.array([0, 0, 1]) # Inner line for p1, p2 in zip(vpo[:-1], vpo[1:]): xvec = p2 - p1 yvec = unit_vector(calc_yvec(xvec, zvec)) new_point = p1 + yvec * (self._thickness / 2) + yvec * self.offset if prev_xvec is not None: if is_parallel(xvec, prev_xvec) is False: prev_p = area_points[-1] # next_point = p2 + yvec * (self._thickness / 2) + yvec * self.offset # c_p = prev_yvec * (self._thickness / 2) + prev_yvec * self.offset AB = prev_xvec CD = xvec s, t = intersect_calc(prev_p, new_point, AB, CD) sAB = prev_p + s * AB new_point = sAB area_points.append(new_point) prev_xvec = xvec prev_yvec = yvec # Add last point area_points.append((p2 + yvec * (self._thickness / 2) + yvec * self.offset)) area_points.append((p2 - yvec * (self._thickness / 2) + yvec * self.offset)) reverse_points = [] # Outer line prev_xvec = None prev_yvec = None for p1, p2 in zip(vpo[:-1], vpo[1:]): xvec = p2 - p1 yvec = unit_vector(calc_yvec(xvec, zvec)) new_point = p1 - yvec * (self._thickness / 2) + yvec * self.offset if prev_xvec is not None: if is_parallel(xvec, prev_xvec) is False: prev_p = reverse_points[-1] c_p = prev_yvec * (self._thickness / 2) - prev_yvec * self.offset new_point -= c_p reverse_points.append(new_point) prev_xvec = xvec prev_yvec = yvec reverse_points.reverse() area_points += reverse_points new_points = [] for p in area_points: new_points.append(tuple([float(c) for c in p])) return new_points
def get_segment_props(self, wall_segment): """ :param wall_segment: :return: """ if wall_segment > len(self._segments): raise ValueError(f"Wall segment id should be equal or less than {len(self._segments)}") p1, p2 = self._segments[wall_segment] xvec = unit_vector(np.array(p2) - np.array(p1)) zvec = np.array([0, 0, 1]) yvec = unit_vector(np.cross(zvec, xvec)) return xvec, yvec, zvec
def generate_ifc_cylinder_geom(shape: PrimCyl, f): """Create IfcExtrudedAreaSolid from primitive PrimCyl""" p1 = shape.p1 p2 = shape.p2 r = shape.r vec = np.array(p2) - np.array(p1) uvec = unit_vector(vec) vecdir = to_real(uvec) cr_dir = np.array([0, 0, 1]) if vector_length(abs(uvec) - abs(cr_dir)) == 0.0: cr_dir = np.array([1, 0, 0]) perp_dir = np.cross(uvec, cr_dir) if vector_length(perp_dir) == 0.0: raise ValueError("Perpendicular dir cannot be zero") create_ifc_placement(f, to_real(p1), vecdir, to_real(perp_dir)) opening_axis_placement = create_ifc_placement(f, to_real(p1), vecdir, to_real(perp_dir)) depth = vector_length(vec) profile = f.createIfcCircleProfileDef("AREA", shape.name, None, r) return create_ifcextrudedareasolid(f, profile, opening_axis_placement, Z, depth)
def local_x(self) -> np.ndarray: if self._local_x is not None: return self._local_x el = self.elset.members[0] if self.type == ElemType.LINE: vec = unit_vector(el.nodes[-1].p - el.nodes[0].p) elif self.type == ElemType.SHELL: vec = unit_vector(el.nodes[1].p - el.nodes[0].p) else: from ada.core.constants import X vec = np.array(X, dtype=float) self._local_x = np.round(np.where(abs(vec) == 0, 0, vec), 8) return self._local_x
def add_hinge_prop_to_elem(elem: Elem, members, hinges_global, xvec, yvec) -> None: """Add hinge property to element from sesam FEM file""" from ada.fem.elements import Hinge, HingeProp if len(elem.nodes) > 2: raise ValueError( "This algorithm was not designed for more than 2 noded elements") for i, x in enumerate(members): if i >= len(elem.nodes): break if x == 0: continue if x not in hinges_global.keys(): raise ValueError("fixno not found!") opt, trano, a1, a2, a3, a4, a5, a6 = hinges_global[x] n = elem.nodes[i] if trano > 0: csys = None else: csys = Csys( f"el{elem.id}_hinge{i + 1}_csys", coords=([ unit_vector(xvec) + n.p, unit_vector(yvec) + n.p, n.p ]), parent=elem.parent, ) dofs_origin = [1, 2, 3, 4, 5, 6] dofs = [ int(x) for x, i in zip(dofs_origin, (a1, a2, a3, a4, a5, a6)) if int(i) != 0 ] end = Hinge(retained_dofs=dofs, csys=csys, fem_node=n) if i == 0: elem.hinge_prop = HingeProp(end1=end) else: elem.hinge_prop = HingeProp(end2=end)
def xvec_e(self) -> np.ndarray: """Local X-vector (including eccentricities)""" if self.e1 is not None: p1 = np.array([ float(x) + float(self.e1[i]) for i, x in enumerate(self.n1.p) ]) else: p1 = self.n1.p if self.e2 is not None: p2 = np.array([ float(x) + float(self.e2[i]) for i, x in enumerate(self.n2.p) ]) else: p2 = self.n2.p return unit_vector(p2 - p1)
def modify_beam(bm: Beam, new_nodes) -> Beam: n1, n2 = new_nodes n1_2_n2_vector = unit_vector(n2.p - n1.p) beam_vector = bm.xvec.round(decimals=Settings.precision) if is_parallel(n1_2_n2_vector, bm.xvec) and not is_null_vector( n1_2_n2_vector, bm.xvec): n1, n2 = n2, n1 elif not is_parallel(n1_2_n2_vector, bm.xvec): raise ValueError( f"Unit vector error. Beam.xvec: {beam_vector}, nodes unit_vec: {-1 * n1_2_n2_vector}" ) bm.n1, bm.n2 = n1, n2 return bm
def read_line_section(elem: Elem, fem: FEM, mat: Material, geono, d, lcsysd, hinges_global, eccentricities): transno = str_to_int(d["transno"]) sec = fem.parent.sections.get_by_id(geono) n1, n2 = elem.nodes v = n2.p - n1.p if vector_length(v) == 0.0: xvec = [1, 0, 0] else: xvec = unit_vector(v) zvec = lcsysd[transno] crossed = np.cross(zvec, xvec) ma = max(abs(crossed)) yvec = tuple([roundoff(x / ma, 3) for x in crossed]) fix_data = str_to_int(d["fixno"]) ecc_data = str_to_int(d["eccno"]) members = None if d["members"] is not None: members = [ str_to_int(x) for x in d["members"].replace("\n", " ").split() ] if fix_data == -1: add_hinge_prop_to_elem(elem, members, hinges_global, xvec, yvec) if ecc_data == -1: add_ecc_to_elem(elem, members, eccentricities, fix_data) fem_set = FemSet(sec.name, [elem], "elset", metadata=dict(internal=True), parent=fem) fem.sets.add(fem_set, append_suffix_on_exist=True) fem_sec = FemSection( sec_id=geono, name=sec.name, sec_type=ElemType.LINE, elset=fem_set, section=sec, local_z=zvec, local_y=yvec, material=mat, parent=fem, ) return fem_sec
def local_z(self) -> np.ndarray: """Local Z describes the up vector of the cross section""" if self._local_z is not None: return self._local_z if self.type == ElemType.LINE: n1, n2 = self.elset.members[0].nodes[0], self.elset.members[0].nodes[-1] v = n2.p - n1.p if vector_length(v) == 0.0: logging.error(f"Element {self.elset.members[0].id} has zero length") xvec = [1, 0, 0] else: xvec = unit_vector(v) self._local_z = calc_zvec(xvec, self.local_y) elif self.type == ElemType.SHELL: self._local_z = normal_to_points_in_plane([n.p for n in self.elset.members[0].nodes]) else: from ada.core.constants import Z self._local_z = np.array(Z, dtype=float) return self._local_z
def _init_orientation(self, angle=None, up=None) -> None: xvec = unit_vector(self.n2.p - self.n1.p) tol = 1e-3 zvec = calc_zvec(xvec) gup = np.array(zvec) if up is None: if angle != 0.0 and angle is not None: from pyquaternion import Quaternion my_quaternion = Quaternion(axis=xvec, degrees=angle) rot_mat = my_quaternion.rotation_matrix up = np.array([ roundoff(x) if abs(x) != 0.0 else 0.0 for x in np.matmul(gup, np.transpose(rot_mat)) ]) else: up = np.array( [roundoff(x) if abs(x) != 0.0 else 0.0 for x in gup]) yvec = calc_yvec(xvec, up) else: if (len(up) == 3) is False: raise ValueError("Up vector must be length 3") if vector_length(xvec - up) < tol: raise ValueError( "The assigned up vector is too close to your beam direction" ) yvec = calc_yvec(xvec, up) # TODO: Fix improper calculation of angle (e.g. xvec = [1,0,0] and up = [0,1,0] should be 270? rad = angle_between(up, yvec) angle = np.rad2deg(rad) up = np.array(up) # lup = np.cross(xvec, yvec) self._xvec = xvec self._yvec = np.array([roundoff(x) for x in yvec]) self._up = up self._angle = angle
def local_y(self) -> np.ndarray: """Local y describes the cross vector of the beams X and Z axis""" if self._local_y is not None: return self._local_y if self.type == ElemType.LINE: el = self.elset.members[0] n1, n2 = el.nodes[0], el.nodes[-1] v = n2.p - n1.p if vector_length(v) == 0.0: raise ValueError(f'Element "{el}" has no length. UNable to calculate y-vector') xvec = unit_vector(v) # See https://en.wikipedia.org/wiki/Cross_product#Coordinate_notation for order of cross product vec = calc_yvec(xvec, self.local_z) elif self.type == ElemType.SHELL: vec = calc_yvec(self.local_x, self.local_z) else: vec = calc_yvec(self.local_x, self.local_z) self._local_y = np.where(abs(vec) == 0, 0, vec) return self._local_y
def sweep_pipe(edge, xvec, r, wt, geom_repr=ElemType.SOLID): if geom_repr not in [ElemType.SOLID, ElemType.SHELL]: raise ValueError("Sweeping pipe must be either 'solid' or 'shell'") t = TopologyExplorer(edge) points = [v for v in t.vertices()] point = BRep_Tool_Pnt(points[0]) # x, y, z = point.X(), point.Y(), point.Z() direction = gp_Dir(*unit_vector(xvec).astype(float).tolist()) # pipe makeWire = BRepBuilderAPI_MakeWire() makeWire.Add(edge) makeWire.Build() wire = makeWire.Wire() try: if geom_repr == ElemType.SOLID: i = make_circular_sec_face(point, direction, r - wt) elbow_i = BRepOffsetAPI_MakePipe(wire, i).Shape() o = make_circular_sec_face(point, direction, r) elbow_o = BRepOffsetAPI_MakePipe(wire, o).Shape() else: elbow_i = None o = make_circular_sec_wire(point, direction, r) elbow_o = BRepOffsetAPI_MakePipe(wire, o).Shape() except RuntimeError as e: logging.error(f'Pipe sweep failed: "{e}"') return wire if geom_repr == ElemType.SOLID: boolean_result = BRepAlgoAPI_Cut(elbow_o, elbow_i).Shape() if boolean_result.IsNull(): logging.debug("Boolean returns None") else: boolean_result = elbow_o return boolean_result
def __init__( self, points2d=None, origin=None, normal=None, xdir=None, points3d=None, flip_normal=False, tol=1e-3, is_closed=True, parent=None, debug=False, ): self._tol = tol self._parent = parent self._is_closed = is_closed self._debug = debug from ada.core.vector_utils import ( is_clockwise, normal_to_points_in_plane, unit_vector, ) if points2d is None and points3d is None: raise ValueError("Either points2d or points3d must be set") if points2d is not None: self._placement = Placement(origin, xdir=xdir, zdir=normal) points3d = self._from_2d_points(points2d) else: normal = normal_to_points_in_plane([np.array(x[:3]) for x in points3d]) p1 = np.array(points3d[0][:3]).astype(float) p2 = np.array(points3d[1][:3]).astype(float) origin = p1 xdir = unit_vector(p2 - p1) self._placement = Placement(origin, xdir=xdir, zdir=normal) points2d = self._from_3d_points(points3d) if is_clockwise(points2d) is False: if is_closed: points2d = [points2d[0]] + [p for p in reversed(points2d[1:])] points3d = [points3d[0]] + [p for p in reversed(points3d[1:])] else: points2d = [p for p in reversed(points2d)] points3d = [p for p in reversed(points3d)] self._points3d = points3d self._points2d = points2d if flip_normal: self.placement.zdir *= -1 self._seg_list = None self._seg_index = None self._face = None self._wire = None self._edges = None self._seg_global_points = None self._nodes = None self._ifc_elem = None self._local2d_to_polycurve(points2d, tol)
def make_cylinder_from_points(p1, p2, r, t=None): vec = unit_vector(np.array(p2) - np.array(p1)) l = vector_length(np.array(p2) - np.array(p1)) return make_cylinder(p1, vec, l, r, t)
def _build_pipe(self): from ada.core.curve_utils import make_arc_segment segs = [] for p1, p2 in zip(self.points[:-1], self.points[1:]): if vector_length(p2.p - p1.p) == 0.0: logging.info("skipping zero length segment") continue segs.append([p1, p2]) segments = segs seg_names = Counter(prefix=self.name + "_") # Make elbows and adjust segments props = dict(section=self.section, material=self.material, parent=self, units=self.units) angle_tol = 1e-1 len_tol = _Settings.point_tol if self.units == "m" else _Settings.point_tol * 1000 for i, (seg1, seg2) in enumerate(zip(segments[:-1], segments[1:])): p11, p12 = seg1 p21, p22 = seg2 vlen1 = vector_length(seg1[1].p - seg1[0].p) vlen2 = vector_length(seg2[1].p - seg2[0].p) if vlen1 < len_tol or vlen2 == len_tol: logging.error( f'Segment Length is below point tolerance for unit "{self.units}". Skipping' ) continue xvec1 = unit_vector(p12.p - p11.p) xvec2 = unit_vector(p22.p - p21.p) a = angle_between(xvec1, xvec2) res = True if abs(abs(a) - abs(np.pi)) < angle_tol or abs( abs(a) - 0.0) < angle_tol else False if res is True: self._segments.append( PipeSegStraight(next(seg_names), p11, p12, **props)) else: if p12 != p21: logging.error("No shared point found") if i != 0 and len(self._segments) > 0: pseg = self._segments[-1] prev_p = (pseg.p1.p, pseg.p2.p) else: prev_p = (p11.p, p12.p) try: seg1, arc, seg2 = make_arc_segment( prev_p[0], prev_p[1], p22.p, self.pipe_bend_radius * 0.99) except ValueError as e: logging.error( f"Error: {e}" ) # , traceback: "{traceback.format_exc()}"') continue except RuntimeError as e: logging.error( f"Error: {e}" ) # , traceback: "{traceback.format_exc()}"') continue if i == 0 or len(self._segments) == 0: self._segments.append( PipeSegStraight(next(seg_names), Node(seg1.p1, units=self.units), Node(seg1.p2, units=self.units), **props)) else: if len(self._segments) == 0: continue pseg = self._segments[-1] pseg.p2 = Node(seg1.p2, units=self.units) self._segments.append( PipeSegElbow( next(seg_names) + "_Elbow", Node(seg1.p1, units=self.units), Node(p21.p, units=self.units), Node(seg2.p2, units=self.units), arc.radius, **props, arc_seg=arc, )) self._segments.append( PipeSegStraight(next(seg_names), Node(seg2.p1, units=self.units), Node(seg2.p2, units=self.units), **props))