Mod: shaping is stable now

Mod: shaping improved documentation
Remove: not necessary modules or moved
This commit is contained in:
L-Nafaryus 2022-01-22 22:08:47 +05:00
parent ac9e938a50
commit 00f7279c6d
No known key found for this signature in database
GPG Key ID: C76D8DCD2727DBB7
11 changed files with 647 additions and 690 deletions

View File

@ -1,17 +1,16 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# This file is part of anisotropy.
# License: GNU GPL version 3, see the file "LICENSE" for details.
from . import utils from . import utils
from . import conversion from . import conversion
from . import metrics from . import metrics
from .mesh import Mesh from .mesh import Mesh, MeshingParameters
__all__ = [ __all__ = [
"utils", "utils",
"conversion", "conversion",
"metrics", "metrics",
"Mesh" "Mesh",
"MeshingParameters"
] ]

View File

@ -196,7 +196,7 @@ class Mesh:
mesh.write(path) mesh.write(path)
@property @property
def volumes(self) -> list[ndarray]: # delete? def volumes(self) -> list[ndarray]:
"""Volumes. """Volumes.
:return: :return:
@ -220,7 +220,7 @@ class Mesh:
return np.sum([ metrics.volume(cell) for cell in self.volumes ]) return np.sum([ metrics.volume(cell) for cell in self.volumes ])
@property @property
def faces(self) -> list[ndarray]: # delete? def faces(self) -> list[ndarray]:
"""Boundary faces. """Boundary faces.
:return: :return:

View File

@ -1,9 +1,21 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# This file is part of anisotropy. """
# License: GNU GPL version 3, see the file "LICENSE" for details. Shaping is a library for using OCC shapes, provides more convient
functionality with power NumPy and Python OOP and contains interesing
primitives.
"""
from .shape import ShapeError, Shape from . import utils
from .shape import Shape
from .periodic import Periodic from .periodic import Periodic
from .simple import Simple
from .faceCentered import FaceCentered from . import primitives
from .bodyCentered import BodyCentered
__all__ = [
"utils",
"primitives",
"Shape",
"Periodic"
]

View File

@ -1,173 +0,0 @@
# -*- coding: utf-8 -*-
# This file is part of anisotropy.
# License: GNU GPL version 3, see the file "LICENSE" for details.
from netgen.occ import *
import numpy
from numpy import pi, sqrt, arccos
from .occExtended import *
from . import Periodic
from . import ShapeError
class BodyCentered(Periodic):
def __init__(
self,
direction: list = None,
**kwargs
):
Periodic.__init__(
self,
alpha = kwargs.get("alpha", 0.01),
r0 = kwargs.get("r0", 1),
filletsEnabled = kwargs.get("filletsEnabled", True),
gamma = pi - 2 * arccos(sqrt(2 / 3))
)
# Parameters
self.direction = direction
self.alphaMin = 0.01
self.alphaMax = 0.18
# Objects
self.lattice = None
self.cell = None
self.shape = None
@property
def L(self):
return self.r0 * 4 / sqrt(3)
def build(self):
#
zero = Pnt(0, 0, 0)
# Lattice
spheres = numpy.array([], dtype = object)
for zn in range(3):
z = zn * self.L
z2 = z - 0.5 * self.L
for yn in range(3):
y = yn * self.L
y2 = y - 0.5 * self.L
for xn in range(3):
x = xn * self.L
x2 = x - 0.5 * self.L
spheres = numpy.append(spheres, Sphere(Pnt(x, y, z), self.radius))
# shifted
spheres = numpy.append(spheres, Sphere(Pnt(x2, y2, z2), self.radius))
lattice = spheres.sum()
lattice = lattice.Scale(zero, self.maximize)
if self.filletsEnabled:
lattice = lattice.MakeFillet(lattice.edges, self.fillets * self.maximize)
self.lattice = lattice.Scale(zero, self.minimize)
# Inlet face
if (self.direction == numpy.array([1., 0., 0.])).prod():
length = 2 * self.r0
width = self.L / 2
diag = self.L * sqrt(2)
height = self.L
xl = sqrt(diag ** 2 + diag ** 2) * 0.5
yw = xl
zh = height
vertices = numpy.array([
(xl, 0, 0),
(0, yw, 0),
(0, yw, zh),
(xl, 0, zh)
])
extr = diag
elif (self.direction == numpy.array([0., 0., 1.])).prod():
length = 2 * self.r0
width = self.L / 2
diag = self.L * sqrt(2)
height = self.L
xl = sqrt(diag ** 2 + diag ** 2) * 0.5
yw = xl
zh = height
vertices = numpy.array([
(0, yw, 0),
(xl, 0, 0),
(2 * xl, yw, 0),
(xl, 2 * yw, 0)
])
extr = height
elif (self.direction == numpy.array([1., 1., 1.])).prod():
length = 2 * self.r0
width = self.L / 2
diag = self.L * sqrt(2)
height = diag / 3
xl = -self.L / 4
yw = -self.L / 4
zh = -self.L / 4
vertices = numpy.array([
(self.L / 3 + xl, self.L / 3 + yw, 4 * self.L / 3 + zh),
(self.L + xl, 0 + yw, self.L + zh),
(4 * self.L / 3 + xl, self.L / 3 + yw, self.L / 3 + zh),
(self.L + xl, self.L + yw, 0 + zh),
(self.L / 3 + xl, 4 * self.L / 3 + yw, self.L / 3 + zh),
(0 + xl, self.L + yw, self.L + zh)
])
extr = self.L * sqrt(3)
else:
raise Exception(f"Direction { self.direction } is not implemented")
# Cell
circuit = Wire([ Segment(Pnt(*v1), Pnt(*v2)) for v1, v2 in zip(vertices, numpy.roll(vertices, -1, axis = 0)) ])
inletface = Face(circuit)
inletface.name = "inlet"
vecFlow = self.normal(inletface)
# ISSUE: don't use face.Extrude(length), only face.Extrude(length, vector)
self.cell = inletface.Extrude(extr, Vec(*vecFlow))
# Boundaries
symetryId = 0
for face in self.cell.faces:
fNorm = self.normal(face)
fAngle = self.angle(vecFlow, fNorm)
if fAngle == 0 or fAngle == numpy.pi:
if (face.center.pos() == inletface.center.pos()).prod():
face.name = "inlet"
else:
face.name = "outlet"
else:
face.name = f"symetry{ symetryId }"
symetryId += 1
# Main shape
self.shape = self.cell - self.lattice
if not len(self.shape.solids) == 1:
raise ShapeError("Expected single solid shape")
else:
self.shape = self.shape.solids[0]
# Boundaries (walls)
for face in self.shape.faces:
if face.name not in ["inlet", "outlet", *[ f"symetry{ n }" for n in range(6) ]]:
face.name = "wall"

View File

@ -1,182 +0,0 @@
# -*- coding: utf-8 -*-
# This file is part of anisotropy.
# License: GNU GPL version 3, see the file "LICENSE" for details.
from netgen.occ import *
import numpy
from numpy import pi, sqrt
from .occExtended import *
from . import Periodic
from . import ShapeError
class FaceCentered(Periodic):
def __init__(
self,
direction: list = None,
**kwargs
):
Periodic.__init__(
self,
alpha = kwargs.get("alpha", 0.01),
r0 = kwargs.get("r0", 1),
filletsEnabled = kwargs.get("filletsEnabled", True),
gamma = 2 / 3 * pi
)
# Parameters
self.direction = direction
self.alphaMin = 0.01
self.alphaMax = 0.13
# Objects
self.lattice = None
self.cell = None
self.shape = None
@property
def L(self):
return self.r0 * 4 / sqrt(2)
def build(self):
#
zero = Pnt(0, 0, 0)
# Lattice
spheres = numpy.array([], dtype = object)
x0 = 0#-0.5 * self.L * (3 - 1)
x20 = 0#-0.5 * self.L * 3
z0 = -0.5 * self.L * (3 - 2)
z20 = -0.5 * self.L * (3 - 1)
for zn in range(3):
z = z0 + zn * self.L
z2 = z20 + zn * self.L
for yn in range(3):
y = yn * 2 * self.r0
y2 = yn * 2 * self.r0 + self.r0
for xn in range(3):
x = x0 + xn * 2 * self.r0
x2 = x20 + xn * 2 * self.r0 + self.r0
# TODO: fix rotations (arcs intersection -> incorrect boolean operations
spheres = numpy.append(spheres, Sphere(Pnt(x, y, z), self.radius).Rotate(Axis(Pnt(x, y, z), X), 45).Rotate(Axis(Pnt(x, y, z), Z), 45))
# shifted
spheres = numpy.append(spheres, Sphere(Pnt(x2, y2, z2), self.radius).Rotate(Axis(Pnt(x2, y2, z2), X), 45).Rotate(Axis(Pnt(x2, y2, z2), Z), 45))
lattice = spheres.sum()
lattice = lattice.Move(Vec(-self.r0 * 2, -self.r0 * 2, 0)).Rotate(Axis(zero, Z), 45)
lattice = lattice.Scale(zero, self.maximize)
if self.filletsEnabled:
lattice = lattice.MakeFillet(lattice.edges, self.fillets * self.maximize)
self.lattice = lattice.Scale(zero, self.minimize)
# Inlet face
if (self.direction == numpy.array([1., 0., 0.])).prod():
length = 2 * self.r0
width = self.L / 2
diag = self.L * sqrt(3)
height = diag / 3
xl = sqrt(length ** 2 + length ** 2) * 0.5
yw = xl
zh = width
vertices = numpy.array([
(0, 0, -zh),
(-xl, yw, -zh),
(-xl, yw, zh),
(0, 0, zh)
])
extr = length
elif (self.direction == numpy.array([0., 0., 1.])).prod():
length = 2 * self.r0
width = self.L / 2
diag = self.L * sqrt(3)
height = diag / 3
xl = sqrt(length ** 2 + length ** 2) * 0.5
yw = xl
zh = width
vertices = numpy.array([
(0, 0, -zh),
(xl, yw, -zh),
(0, 2 * yw, -zh),
(-xl, yw, -zh)
])
extr = 2 * width
elif (self.direction == numpy.array([1., 1., 1.])).prod():
length = 2 * self.r0
width = self.L / 2
diag = self.L * sqrt(3)
height = diag / 3
xl = -(3 - 2) * self.L / 3
yw = -(3 - 2) * self.L / 3
zh = -(3 - 2) * self.L / 3
vertices = numpy.array([
(-2 * width / 3 + xl, -2 * width / 3 + yw, width / 3 + zh),
(0 + xl, -width + yw, 0 + zh),
(width / 3 + xl, -2 * width / 3 + yw, -2 * width / 3 + zh),
(0 + xl, 0 + yw, -width + zh),
(-2 * width / 3 + xl, width / 3 + yw, -2 * width / 3 + zh),
(-width + xl, 0 + yw, 0 + zh)
])
extr = sqrt(3) * self.L
else:
raise Exception(f"Direction { self.direction } is not implemented")
# Cell
circuit = Wire([ Segment(Pnt(*v1), Pnt(*v2)) for v1, v2 in zip(vertices, numpy.roll(vertices, -1, axis = 0)) ])
inletface = Face(circuit)
inletface.name = "inlet"
vecFlow = self.normal(inletface)
# ISSUE: don't use face.Extrude(length), only face.Extrude(length, vector)
self.cell = inletface.Extrude(extr, Vec(*vecFlow))
# Boundaries
symetryId = 0
for face in self.cell.faces:
fNorm = self.normal(face)
fAngle = self.angle(vecFlow, fNorm)
if fAngle == 0 or fAngle == numpy.pi:
if (face.center.pos() == inletface.center.pos()).prod():
face.name = "inlet"
else:
face.name = "outlet"
else:
face.name = f"symetry{ symetryId }"
symetryId += 1
# Main shape
self.shape = self.cell - self.lattice
if not len(self.shape.solids) == 1:
raise ShapeError("Expected single solid shape")
else:
self.shape = self.shape.solids[0]
# Boundaries (walls)
for face in self.shape.faces:
if face.name not in ["inlet", "outlet", *[ f"symetry{ n }" for n in range(6) ]]:
face.name = "wall"

View File

@ -1,41 +0,0 @@
# -*- coding: utf-8 -*-
# This file is part of anisotropy.
# License: GNU GPL version 3, see the file "LICENSE" for details.
from functools import wraps
from netgen import occ
import numpy
def add_method(cls):
"""Add method to existing class. Use it as decorator.
"""
def decorator(func):
@wraps(func)
def wrapper(self, *args, **kwargs):
return func(self, *args, **kwargs)
setattr(cls, func.__name__, wrapper)
return func
return decorator
@add_method(occ.gp_Pnt)
def pos(self) -> numpy.array:
return numpy.array([self.x, self.y, self.z])
# ISSUE: netgen.occ.Face.Extrude: the opposite face has the same name and normal vector as an initial face.
#def reconstruct(shape):
# """Reconstruct shape with new objects.
# """
# faces = []
#
# for face in shape.faces:
# faceNew = occ.Face(face.wires[0])
# faceNew.name = face.name
# faces.append(faceNew)
#
# return occ.Solid(faces)

View File

@ -1,9 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# This file is part of anisotropy.
# License: GNU GPL version 3, see the file "LICENSE" for details.
from netgen.occ import * import numpy as np
from numpy import pi, sqrt, cos, arccos, fix
from . import Shape from . import Shape
@ -13,85 +10,60 @@ class Periodic(Shape):
self, self,
alpha: float = None, alpha: float = None,
r0: float = 1, r0: float = 1,
#L: float = None, L: float = None,
#radius: float = None,
filletsEnabled: bool = True, filletsEnabled: bool = True,
#fillets: float = None,
gamma = None, gamma = None,
**kwargs **kwargs
): ):
"""Constructor method. """A Periodic object contains periodic structure.
:param alpha: :param alpha:
Spheres overlap parameter. Spheres overlap parameter.
:param r0: :param r0:
Initial spheres radius. Initial spheres radius.
:param L:
Side length of periodic cell. Depends on r0.
:param gamma:
Angle between lines that connect centers of spheres.
:param filletsEnabled: :param filletsEnabled:
Enable fillets beetween spheres. If True, calculate fillets beetween spheres.
""" """
Shape.__init__(self) Shape.__init__(self)
# Parameters # Parameters
self.alpha = alpha self.alpha = alpha
self.r0 = r0 self.r0 = r0
self.L = L
self.theta = 0.5 * pi # for lattice
# self.theta = 0.5 * pi
self.gamma = gamma or pi - 2 * (0.5 * self.theta) self.gamma = gamma or np.pi - 2 * (0.5 * 0.5 * np.pi)
self.filletsEnabled = filletsEnabled self.filletsEnabled = filletsEnabled
self.filletsScale = 0.8 self.filletsScale = 0.8
# scale parameters for precision boolean operations # scale parameters for precision boolean operations
self.maximize = 1e+2 self.maximize = 1e+2
self.minimize = 1e-2 self.minimize = 1e-2
@property @property
def L(self): def radius(self) -> float:
"""(Override) Parameter depending on the ``r0``.
"""
pass
@property
def radius(self):
"""Spheres radius """Spheres radius
:return:
Radius.
""" """
return self.r0 / (1 - self.alpha) return self.r0 / (1 - self.alpha)
@property @property
def fillets(self): def fillets(self):
analytical = self.r0 * sqrt(2) / sqrt(1 - cos(self.gamma)) - self.radius """Fillets radius beetween spheres.
# ISSUE: MakeFilletAll : Fillet can't be computed on the given shape with the given radius.
# Temporary solution: degrade the precision (minRound <= analytical). :return:
Radius.
"""
analytical = self.r0 * np.sqrt(2) / np.sqrt(1 - np.cos(self.gamma)) - self.radius
rTol = 3 rTol = 3
minRound = fix(10 ** rTol * analytical) * 10 ** -rTol minRound = np.fix(10 ** rTol * analytical) * 10 ** -rTol
return minRound * self.filletsScale return minRound * self.filletsScale
def lattice(self,
theta = 0.5 * pi
):
zero = Pnt(0, 0, 0)
maximize = 1e+2
minimize = 1e-2
# Lattice
spheres = numpy.array([], dtype = object)
for zn in range(3):
z = zn * self.L
for yn in range(3):
y = yn * self.L
for xn in range(3):
x = xn * self.L
spheres = numpy.append(spheres, Sphere(Pnt(x, y, z), self.radius))
lattice = spheres.sum()
lattice = lattice.Scale(zero, maximize)
if self.filletsEnabled:
lattice = lattice.MakeFillet(lattice.edges, self.fillets * maximize)
self.lattice = lattice.Scale(zero, minimize)

View File

@ -0,0 +1,499 @@
# -*- coding: utf-8 -*-
from __future__ import annotations
from numpy import ndarray
import numpy as np
import netgen.occ as ng_occ
from . import Periodic
from . import utils
def simple(alpha: float, direction: list | ndarray, **kwargs) -> Periodic:
"""Simple periodic structure.
:param alpha:
Spheres overlap parameter.
:param direction:
Flow direction vector. This parameter affects the geometry type
and boundary (faces) names.
:return:
Periodic object.
"""
# base object
pc = Periodic(
alpha = alpha,
gamma = np.pi - 2 * (0.5 * 0.5 * np.pi)
)
# additional parameters
pc.__dict__.update(kwargs)
pc.L = 2 * pc.r0
pc.direction = np.asarray(direction)
# additional attributes
pc.cell: ng_occ.Solid = None
pc.lattice: ng_occ.Solid = None
# constants
zero = ng_occ.Pnt(0, 0, 0)
# Lattice
spheres = []
for zn in range(3):
z = zn * pc.L
for yn in range(3):
y = yn * pc.L
for xn in range(3):
x = xn * pc.L
spheres.append(ng_occ.Sphere(ng_occ.Pnt(x, y, z), pc.radius))
lattice = np.sum(spheres)
lattice = lattice.Scale(zero, pc.maximize)
if pc.filletsEnabled:
lattice = lattice.MakeFillet(lattice.edges, pc.fillets * pc.maximize)
pc.lattice = lattice.Scale(zero, pc.minimize)
# Inlet face
if np.all(pc.direction == [1., 0., 0.]):
length = pc.L * np.sqrt(2)
width = pc.L * np.sqrt(2)
height = pc.L
xl = np.sqrt(length ** 2 * 0.5)
yw = xl
zh = height
vertices = np.array([
(xl, 0, 0),
(0, yw, 0),
(0, yw, zh),
(xl, 0, zh)
])
extr = width
elif np.all(pc.direction == [0., 0., 1.]):
length = pc.L * np.sqrt(2)
width = pc.L * np.sqrt(2)
height = pc.L
xl = np.sqrt(length ** 2 * 0.5)
yw = xl
zh = height
vertices = np.array([
(0, yw, 0),
(xl, 0, 0),
(2 * xl, yw, 0),
(xl, 2 * yw, 0)
])
extr = height
elif np.all(pc.direction == [1., 1., 1.]):
length = pc.L * np.sqrt(2)
width = pc.L * np.sqrt(2)
height = pc.L
xl = -pc.L - pc.L / 6
yw = -pc.L - pc.L / 6
zh = -pc.L / 6
vertices = np.array([
(pc.L + xl, pc.L + yw, pc.L + zh),
(5 * pc.L / 3 + xl, 2 * pc.L / 3 + yw, 2 * pc.L / 3 + zh),
(2 * pc.L + xl, pc.L + yw, 0 + zh),
(5 * pc.L / 3 + xl, 5 * pc.L / 3 + yw, -pc.L / 3 + zh),
(pc.L + xl, 2 * pc.L + yw, 0 + zh),
(2 * pc.L / 3 + xl, 5 * pc.L / 3 + yw, 2 * pc.L / 3 + zh)
])
extr = pc.L * np.sqrt(3)
else:
raise Exception(f"Direction { pc.direction } is not implemented")
# Cell
circuit = ng_occ.Wire([
ng_occ.Segment(ng_occ.Pnt(*v1), ng_occ.Pnt(*v2))
for v1, v2 in zip(vertices, np.roll(vertices, -1, axis = 0))
])
inletface = ng_occ.Face(circuit)
inletface.name = "inlet"
vecFlow = utils.normal(inletface)
pc.cell = inletface.Extrude(extr, ng_occ.Vec(*vecFlow))
# Boundary faces
symetryId = 0
for face in pc.cell.faces:
fNorm = utils.normal(face)
fAngle = utils.angle(vecFlow, fNorm)
if fAngle == 0 or fAngle == np.pi:
if np.all(utils.pos(face.center) == utils.pos(inletface.center)):
face.name = "inlet"
else:
face.name = "outlet"
else:
face.name = f"symetry{ symetryId }"
symetryId += 1
# Main shape
pc.shape = pc.cell - pc.lattice
assert len(pc.shape.solids) == 1, "Expected single solid shape"
pc.shape = pc.shape.solids[0]
# Boundary faces (walls)
for face in pc.shape.faces:
if face.name not in ["inlet", "outlet", *[ f"symetry{ n }" for n in range(6) ]]:
face.name = "wall"
return pc
def body_centered(alpha: float, direction: list | ndarray, **kwargs) -> Periodic:
"""Body centered periodic structure.
:param alpha:
Spheres overlap parameter.
:param direction:
Flow direction vector. This parameter affects the geometry type
and boundary (faces) names.
:return:
Periodic object.
"""
# base object
pc = Periodic(
alpha = alpha,
gamma = np.pi - 2 * np.arccos(np.sqrt(2 / 3))
)
# additional parameters
pc.__dict__.update(kwargs)
pc.L = 4 / np.sqrt(3) * pc.r0
pc.direction = np.asarray(direction)
# additional attributes
pc.cell: ng_occ.Solid = None
pc.lattice: ng_occ.Solid = None
# constants
zero = ng_occ.Pnt(0, 0, 0)
# Lattice
spheres = []
for zn in range(3):
z = zn * pc.L
z2 = z - 0.5 * pc.L
for yn in range(3):
y = yn * pc.L
y2 = y - 0.5 * pc.L
for xn in range(3):
x = xn * pc.L
x2 = x - 0.5 * pc.L
spheres.append(ng_occ.Sphere(ng_occ.Pnt(x, y, z), pc.radius))
# shifted
spheres.append(ng_occ.Sphere(ng_occ.Pnt(x2, y2, z2), pc.radius))
lattice = np.sum(spheres)
lattice = lattice.Scale(zero, pc.maximize)
if pc.filletsEnabled:
lattice = lattice.MakeFillet(lattice.edges, pc.fillets * pc.maximize)
pc.lattice = lattice.Scale(zero, pc.minimize)
# Inlet face
if np.all(pc.direction == [1., 0., 0.]):
# length = 2 * pc.r0
# width = pc.L / 2
diag = pc.L * np.sqrt(2)
height = pc.L
xl = np.sqrt(diag ** 2 + diag ** 2) * 0.5
yw = xl
zh = height
vertices = np.array([
(xl, 0, 0),
(0, yw, 0),
(0, yw, zh),
(xl, 0, zh)
])
extr = diag
elif np.all(pc.direction == [0., 0., 1.]):
# length = 2 * pc.r0
# width = pc.L / 2
diag = pc.L * np.sqrt(2)
height = pc.L
xl = np.sqrt(diag ** 2 + diag ** 2) * 0.5
yw = xl
zh = height
vertices = np.array([
(0, yw, 0),
(xl, 0, 0),
(2 * xl, yw, 0),
(xl, 2 * yw, 0)
])
extr = height
elif np.all(pc.direction == [1., 1., 1.]):
# length = 2 * pc.r0
# width = pc.L / 2
diag = pc.L * np.sqrt(2)
height = diag / 3
xl = -pc.L / 4
yw = -pc.L / 4
zh = -pc.L / 4
vertices = np.array([
(pc.L / 3 + xl, pc.L / 3 + yw, 4 * pc.L / 3 + zh),
(pc.L + xl, 0 + yw, pc.L + zh),
(4 * pc.L / 3 + xl, pc.L / 3 + yw, pc.L / 3 + zh),
(pc.L + xl, pc.L + yw, 0 + zh),
(pc.L / 3 + xl, 4 * pc.L / 3 + yw, pc.L / 3 + zh),
(0 + xl, pc.L + yw, pc.L + zh)
])
extr = pc.L * np.sqrt(3)
else:
raise Exception(f"Direction { pc.direction } is not implemented")
# Cell
circuit = ng_occ.Wire([
ng_occ.Segment(ng_occ.Pnt(*v1), ng_occ.Pnt(*v2))
for v1, v2 in zip(vertices, np.roll(vertices, -1, axis = 0))
])
inletface = ng_occ.Face(circuit)
inletface.name = "inlet"
vecFlow = utils.normal(inletface)
pc.cell = inletface.Extrude(extr, ng_occ.Vec(*vecFlow))
# Boundary faces
symetryId = 0
for face in pc.cell.faces:
fNorm = utils.normal(face)
fAngle = utils.angle(vecFlow, fNorm)
if fAngle == 0 or fAngle == np.pi:
if np.all(utils.pos(face.center) == utils.pos(inletface.center)):
face.name = "inlet"
else:
face.name = "outlet"
else:
face.name = f"symetry{ symetryId }"
symetryId += 1
# Main shape
pc.shape = pc.cell - pc.lattice
assert len(pc.shape.solids) == 1, "Expected single solid shape"
pc.shape = pc.shape.solids[0]
# Boundary faces (walls)
for face in pc.shape.faces:
if face.name not in ["inlet", "outlet", *[ f"symetry{ n }" for n in range(6) ]]:
face.name = "wall"
return pc
def face_centered(alpha: float, direction: list | ndarray, **kwargs) -> Periodic:
"""Face centered periodic structure.
:param alpha:
Spheres overlap parameter.
:param direction:
Flow direction vector. This parameter affects the geometry type
and boundary (faces) names.
:return:
Periodic object.
"""
# base class
pc = Periodic(
alpha = alpha,
gamma = 2 / 3 * np.pi
)
# additional parameters
pc.__dict__.update(kwargs)
pc.L = 4 / np.sqrt(2) * pc.r0
pc.direction = np.asarray(direction)
# additional attributes
pc.cell: ng_occ.Solid = None
pc.lattice: ng_occ.Solid = None
# constants
zero = ng_occ.Pnt(0, 0, 0)
# Lattice
spheres = []
x0 = 0
x20 = 0
z0 = -0.5 * pc.L * (3 - 2)
z20 = -0.5 * pc.L * (3 - 1)
for zn in range(3):
z = z0 + zn * pc.L
z2 = z20 + zn * pc.L
for yn in range(3):
y = yn * 2 * pc.r0
y2 = yn * 2 * pc.r0 + pc.r0
for xn in range(3):
x = x0 + xn * 2 * pc.r0
x2 = x20 + xn * 2 * pc.r0 + pc.r0
# TODO: fix rotations (arcs intersection -> incorrect boolean operations
spheres.append(
ng_occ.Sphere(ng_occ.Pnt(x, y, z), pc.radius)
.Rotate(ng_occ.Axis(ng_occ.Pnt(x, y, z), ng_occ.X), 45)
.Rotate(ng_occ.Axis(ng_occ.Pnt(x, y, z), ng_occ.Z), 45)
)
# shifted
spheres.append(
ng_occ.Sphere(ng_occ.Pnt(x2, y2, z2), pc.radius)
.Rotate(ng_occ.Axis(ng_occ.Pnt(x2, y2, z2), ng_occ.X), 45)
.Rotate(ng_occ.Axis(ng_occ.Pnt(x2, y2, z2), ng_occ.Z), 45)
)
lattice = np.sum(spheres)
lattice = (
lattice.Move(ng_occ.Vec(-pc.r0 * 2, -pc.r0 * 2, 0))
.Rotate(ng_occ.Axis(zero, ng_occ.Z), 45)
)
lattice = lattice.Scale(zero, pc.maximize)
if pc.filletsEnabled:
lattice = lattice.MakeFillet(lattice.edges, pc.fillets * pc.maximize)
pc.lattice = lattice.Scale(zero, pc.minimize)
# Inlet face
if np.all(pc.direction == [1., 0., 0.]):
length = 2 * pc.r0
width = pc.L / 2
# diag = pc.L * np.sqrt(3)
# height = diag / 3
xl = np.sqrt(length ** 2 + length ** 2) * 0.5
yw = xl
zh = width
vertices = np.array([
(0, 0, -zh),
(-xl, yw, -zh),
(-xl, yw, zh),
(0, 0, zh)
])
extr = length
elif np.all(pc.direction == [0., 0., 1.]):
length = 2 * pc.r0
width = pc.L / 2
# diag = pc.L * np.sqrt(3)
# height = diag / 3
xl = np.sqrt(length ** 2 + length ** 2) * 0.5
yw = xl
zh = width
vertices = np.array([
(0, 0, -zh),
(xl, yw, -zh),
(0, 2 * yw, -zh),
(-xl, yw, -zh)
])
extr = 2 * width
elif np.all(pc.direction == [1., 1., 1.]):
length = 2 * pc.r0
width = pc.L / 2
# diag = pc.L * np.sqrt(3)
# height = diag / 3
xl = -(3 - 2) * pc.L / 3
yw = -(3 - 2) * pc.L / 3
zh = -(3 - 2) * pc.L / 3
vertices = np.array([
(-2 * width / 3 + xl, -2 * width / 3 + yw, width / 3 + zh),
(0 + xl, -width + yw, 0 + zh),
(width / 3 + xl, -2 * width / 3 + yw, -2 * width / 3 + zh),
(0 + xl, 0 + yw, -width + zh),
(-2 * width / 3 + xl, width / 3 + yw, -2 * width / 3 + zh),
(-width + xl, 0 + yw, 0 + zh)
])
extr = np.sqrt(3) * pc.L
else:
raise Exception(f"Direction { pc.direction } is not implemented")
# Cell
circuit = ng_occ.Wire([
ng_occ.Segment(ng_occ.Pnt(*v1), ng_occ.Pnt(*v2))
for v1, v2 in zip(vertices, np.roll(vertices, -1, axis = 0))
])
inletface = ng_occ.Face(circuit)
inletface.name = "inlet"
vecFlow = utils.normal(inletface)
pc.cell = inletface.Extrude(extr, ng_occ.Vec(*vecFlow))
# Boundary faces
symetryId = 0
for face in pc.cell.faces:
fNorm = utils.normal(face)
fAngle = utils.angle(vecFlow, fNorm)
if fAngle == 0 or fAngle == np.pi:
if np.all(utils.pos(face.center) == utils.pos(inletface.center)):
face.name = "inlet"
else:
face.name = "outlet"
else:
face.name = f"symetry{ symetryId }"
symetryId += 1
# Main shape
pc.shape = pc.cell - pc.lattice
assert len(pc.shape.solids) == 1, "Expected single solid shape"
pc.shape = pc.shape.solids[0]
# Boundary faces (walls)
for face in pc.shape.faces:
if face.name not in ["inlet", "outlet", *[ f"symetry{ n }" for n in range(6) ]]:
face.name = "wall"
return pc

View File

@ -1,80 +1,95 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# This file is part of anisotropy.
# License: GNU GPL version 3, see the file "LICENSE" for details.
from netgen.occ import * from __future__ import annotations
import numpy from numpy import ndarray
from numpy import linalg
import os
from os import PathLike from os import PathLike
from pathlib import Path
import numpy as np
import netgen.occ as ng_occ
import pathlib
class ShapeError(Exception): from . import utils
pass
class Shape(object): class Shape(object):
def __init__(self): def __init__(self):
"""A Shape object contains OCC shape.
"""
self.groups = {} self.groups = {}
self.shape = None self.shape = None
@property
def geometry(self) -> ng_occ.OCCGeometry:
"""Shape as OCCGeometry object.
"""
return ng_occ.OCCGeometry(self.shape)
@property
def type(self) -> ng_occ.TopAbs_ShapeEnum:
"""Type of the shape. (shortcut)
"""
return self.shape.type
@property
def volume(self) -> float:
"""Volume of the shape. (shortcut)
"""
return self.shape.volume
@property
def center(self) -> ndarray:
"""Center of the shape.
"""
return np.array(utils.pos(self.shape.center))
def write(self, filename: PathLike): def write(self, filename: PathLike):
"""Export a shape. """Export a shape to the file.
Supported formats: step. Supported formats: step.
:param filename: :param filename:
Name of the file to store the given shape in. Path of the file.
:return:
Output, error messages and returncode
""" """
out, err, returncode = "", "", 0 path = pathlib.Path(filename).resolve()
path = Path(filename).resolve()
ext = path.suffix[1: ] ext = path.suffix[1: ]
try: if ext == "step":
if ext == "step": self.shape.WriteStep(str(path))
self.shape.WriteStep(path)
else:
raise NotImplementedError(f"{ ext } is not supported")
except NotImplementedError as e:
err = e
returncode = 1
except Exception as e: else:
err = e raise NotImplementedError(f"Shape format '{ ext }' is not supported")
returncode = 1
return out, err, returncode
def read(self, filename: PathLike): def read(self, filename: PathLike):
path = Path(filename).resolve() """Import a shape from the file.
Supported formats: step, iges, brep.
:param filename:
Path of the file.
"""
path = pathlib.Path(filename).resolve()
ext = path.suffix[1: ] ext = path.suffix[1: ]
if ext in ["step", "iges", "brep"]: if ext in ["step", "iges", "brep"]:
self.shape = OCCGeometry(path).shape self.shape = ng_occ.OCCGeometry(str(path)).shape
else: else:
raise NotImplementedError(f"Shape format '{ext}' is not supported") raise NotImplementedError(f"Shape format '{ext}' is not supported")
return self return self
def patches(self, group: bool = False, shiftIndex: bool = False, prefix: str = None): def patches(
self,
group: bool = False,
shiftIndex: bool = False,
prefix: str = None
) -> list | dict:
"""Get patches indices with their names. """Get patches indices with their names.
:param group: :param group:
Group indices together with the same patches names. Group indices together with the same patches names.
:param shiftIndex: :param shiftIndex:
Start numerating with one instead of zero. Start numerating with one instead of zero.
:param prefix: :param prefix:
Add string prefix to the index. Add string prefix to the index.
:return: :return:
List if group = False else dictionary. List if group = False else dictionary.
""" """
@ -105,24 +120,3 @@ class Shape(object):
patches_.append((item, face.name)) patches_.append((item, face.name))
return patches_ return patches_
def normal(self, face: FACE) -> numpy.array:
"""
:return:
Normal vector to face.
"""
_, u, v = face.surf.D1(0, 0)
return numpy.cross([u.x, u.y, u.z], [v.x, v.y, v.z])
def angle(self, vec1: numpy.array, vec2: numpy.array) -> float:
"""
:return:
Angle between two vectors in radians.
"""
inner = numpy.inner(vec1, vec2)
norms = linalg.norm(vec1) * linalg.norm(vec2)
cos = inner / norms
return numpy.arccos(numpy.clip(cos, -1.0, 1.0))

View File

@ -1,170 +0,0 @@
# -*- coding: utf-8 -*-
# This file is part of anisotropy.
# License: GNU GPL version 3, see the file "LICENSE" for details.
from netgen.occ import *
import numpy
from numpy import pi, sqrt
from .occExtended import *
from . import Periodic
from . import ShapeError
class Simple(Periodic):
def __init__(
self,
direction: list = None,
**kwargs
):
Periodic.__init__(
self,
alpha = kwargs.get("alpha", 0.01),
r0 = kwargs.get("r0", 1),
filletsEnabled = kwargs.get("filletsEnabled", True)
)
# Parameters
self.direction = direction
self.alphaMin = 0.01
self.alphaMax = 0.28
# Objects
self.lattice = None
self.cell = None
self.shape = None
@property
def L(self):
return 2 * self.r0
def build(self):
#
zero = Pnt(0, 0, 0)
# Lattice
spheres = numpy.array([], dtype = object)
for zn in range(3):
z = zn * self.L
for yn in range(3):
y = yn * self.L
for xn in range(3):
x = xn * self.L
spheres = numpy.append(spheres, Sphere(Pnt(x, y, z), self.radius))
lattice = spheres.sum()
lattice = lattice.Scale(zero, self.maximize)
if self.filletsEnabled:
lattice = lattice.MakeFillet(lattice.edges, self.fillets * self.maximize)
self.lattice = lattice.Scale(zero, self.minimize)
# Inlet face
if (self.direction == numpy.array([1., 0., 0.])).prod():
length = self.L * numpy.sqrt(2)
width = self.L * numpy.sqrt(2)
height = self.L
xl = numpy.sqrt(length ** 2 * 0.5)
yw = xl
zh = height
vertices = numpy.array([
(xl, 0, 0),
(0, yw, 0),
(0, yw, zh),
(xl, 0, zh)
])
extr = width
elif (self.direction == numpy.array([0., 0., 1.])).prod():
length = self.L * numpy.sqrt(2)
width = self.L * numpy.sqrt(2)
height = self.L
xl = numpy.sqrt(length ** 2 * 0.5)
yw = xl
zh = height
vertices = numpy.array([
(0, yw, 0),
(xl, 0, 0),
(2 * xl, yw, 0),
(xl, 2 * yw, 0)
])
extr = height
elif (self.direction == numpy.array([1., 1., 1.])).prod():
length = self.L * numpy.sqrt(2)
width = self.L * numpy.sqrt(2)
height = self.L
xl = -self.L - self.L / 6
yw = -self.L - self.L / 6
zh = -self.L / 6
vertices = numpy.array([
(self.L + xl, self.L + yw, self.L + zh),
(5 * self.L / 3 + xl, 2 * self.L / 3 + yw, 2 * self.L / 3 + zh),
(2 * self.L + xl, self.L + yw, 0 + zh),
(5 * self.L / 3 + xl, 5 * self.L / 3 + yw, -self.L / 3 + zh),
(self.L + xl, 2 * self.L + yw, 0 + zh),
(2 * self.L / 3 + xl, 5 * self.L / 3 + yw, 2 * self.L / 3 + zh)
])
extr = self.L * sqrt(3)
else:
raise Exception(f"Direction { self.direction } is not implemented")
#
# Cell
circuit = Wire([ Segment(Pnt(*v1), Pnt(*v2)) for v1, v2 in zip(vertices, numpy.roll(vertices, -1, axis = 0)) ])
inletface = Face(circuit)
inletface.name = "inlet"
vecFlow = self.normal(inletface)
# ISSUE: don't use face.Extrude(length), only face.Extrude(length, vector)
self.cell = inletface.Extrude(extr, Vec(*vecFlow))
# Boundaries
symetryId = 0
for face in self.cell.faces:
fNorm = self.normal(face)
fAngle = self.angle(vecFlow, fNorm)
if fAngle == 0 or fAngle == numpy.pi:
if (face.center.pos() == inletface.center.pos()).prod():
face.name = "inlet"
else:
face.name = "outlet"
else:
face.name = f"symetry{ symetryId }"
symetryId += 1
# Main shape
self.shape = self.cell - self.lattice
if not len(self.shape.solids) == 1:
raise ShapeError("Expected single solid shape")
else:
self.shape = self.shape.solids[0]
# Boundaries (walls)
for face in self.shape.faces:
if face.name not in ["inlet", "outlet", *[ f"symetry{ n }" for n in range(6) ]]:
face.name = "wall"

View File

@ -0,0 +1,47 @@
# -*- coding: utf-8 -*-
from numpy import ndarray
import numpy as np
import netgen.occ as ng_occ
def pos(point: ng_occ.gp_Pnt) -> ndarray:
"""Extract coordinates from point.
:param point:
OCC point object
:return:
Array of coordinates.
"""
return np.array([ point.x, point.y, point.z ])
def normal(face: ng_occ.Face) -> ndarray:
"""Calculate normal vector from face.
:param face:
OCC face object.
:return:
Normal vector from face.
"""
_, u, v = face.surf.D1(0, 0)
return np.cross([u.x, u.y, u.z], [v.x, v.y, v.z])
def angle(vec1: ndarray, vec2: ndarray) -> float:
"""Angle between two vectors.
:param vec1:
Array of points that represents first vector.
:param vec2:
Array of points that represents second vector.
:return:
Angle between two vectors in radians.
"""
inner = np.inner(vec1, vec2)
norms = np.linalg.norm(vec1) * np.linalg.norm(vec2)
cos = inner / norms
return np.arccos(np.clip(cos, -1.0, 1.0))