-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 582ad6b
Showing
8 changed files
with
950 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
__pycache__ | ||
.vscode | ||
even_quad_sphere*.zip |
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
# Even Quad Sphere | ||
|
||
Special case of quad sphere which maximizes equality of edge lengths. | ||
|
||
Quad sphere from "Extra Objects" addon (unequal edge lengths even on axis-aligned circles): | ||
|
||
![Addon preferences](img/quad_sphere_extra_objects_addon.png "Extra Objects addon quad sphere") | ||
|
||
Quad sphere from this addon (equal edge lengths on axis-aligned circles): | ||
|
||
![Addon preferences](img/even_quad_sphere.png "Quad sphere made by this addon") | ||
|
||
## Usage | ||
|
||
Shift+A -> Mesh -> Even Quad Sphere |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
bl_info = { | ||
"name": "Even Quad Sphere", | ||
"description": "Special case of quad sphere which maximizes equality of edge lengths", | ||
"author": "Zyl", | ||
"version": (1, 0), | ||
"blender": (2, 93, 0), | ||
"category": "Add Mesh" | ||
} | ||
|
||
if "bpy" in locals(): | ||
import importlib | ||
importlib.reload(add_mesh_even_quad_sphere) | ||
else: | ||
from . import add_mesh_even_quad_sphere | ||
|
||
import bpy | ||
|
||
addonClasses = [ | ||
add_mesh_even_quad_sphere.AddEvenQuadSphere, | ||
] | ||
|
||
def menu_func(self, context): | ||
layout = self.layout | ||
layout.operator("mesh.primitive_even_quad_sphere_add", | ||
text="Even Quad Sphere", icon="SPHERE") | ||
pass | ||
|
||
def register(): | ||
for addonClass in addonClasses: | ||
bpy.utils.register_class(addonClass) | ||
bpy.types.VIEW3D_MT_mesh_add.append(menu_func) | ||
|
||
|
||
def unregister(): | ||
bpy.types.VIEW3D_MT_mesh_add.remove(menu_func) | ||
for addonClass in reversed(addonClasses): | ||
bpy.utils.unregister_class(addonClass) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,240 @@ | ||
import bpy | ||
from bpy_extras import object_utils | ||
from itertools import permutations | ||
from math import ( | ||
copysign, pi, | ||
sqrt, tan, | ||
) | ||
from bpy.types import Operator | ||
from bpy.props import ( | ||
BoolProperty, | ||
EnumProperty, | ||
FloatProperty, | ||
FloatVectorProperty, | ||
IntProperty, | ||
StringProperty, | ||
) | ||
|
||
|
||
def even_quad_sphere(slices, size): | ||
lines = slices + 1 | ||
# totalVerts = 6 * (slices - 1) * (slices - 1) + 12 * (slices - 1) + 8 | ||
# totalEdges = 12 * slices + 6 * 2 * (slices - 1) * slices | ||
# totalFaces = 6 * slices * slices | ||
verts = [] | ||
sideVerts = [[None]*lines*lines, [None]*lines*lines, [None]*lines*lines, [None]*lines*lines, [None]*lines*lines, [None]*lines*lines] | ||
vertIndex = 0 | ||
|
||
# Top | ||
for z in range(0, slices + 1): | ||
for x in range(0, slices + 1): | ||
i = x + z * lines | ||
sideVerts[0][i] = vertIndex | ||
vertIndex = vertIndex + 1 | ||
verts.append([0.0, 0.0, 0.0]) | ||
|
||
# Front | ||
for y in range(0, slices + 1): | ||
for x in range(0, slices + 1): | ||
i = x + y * lines | ||
if y == slices: | ||
sideVerts[1][i] = sideVerts[0][x] | ||
else: | ||
sideVerts[1][i] = vertIndex | ||
vertIndex = vertIndex + 1 | ||
verts.append([0.0, 0.0, 0.0]) | ||
|
||
# Right | ||
for y in range(0, slices + 1): | ||
for z in range(0, slices + 1): | ||
i = z + y * lines | ||
if z == 0: | ||
sideVerts[2][i] = sideVerts[1][i + slices] | ||
elif y == slices: | ||
sideVerts[2][i] = sideVerts[0][slices + z * lines] | ||
else: | ||
sideVerts[2][i] = vertIndex | ||
vertIndex = vertIndex + 1 | ||
verts.append([0.0, 0.0, 0.0]) | ||
|
||
# Back | ||
for y in range(0, slices + 1): | ||
for x in range(0, slices + 1): | ||
i = x + y * lines | ||
if x == slices: | ||
sideVerts [3][i] = sideVerts[2][i] | ||
elif y == slices: | ||
sideVerts [3][i] = sideVerts[0][i] | ||
else: | ||
sideVerts [3][i] = vertIndex | ||
vertIndex = vertIndex + 1 | ||
verts.append([0.0, 0.0, 0.0]) | ||
|
||
# Left | ||
for y in range(0, slices + 1): | ||
for z in range(0, slices + 1): | ||
i = z + y * lines | ||
if z == 0: | ||
sideVerts [4][i] = sideVerts [1][i] | ||
elif y == slices: | ||
sideVerts [4][i] = sideVerts [0][z * lines] | ||
elif z == slices: | ||
sideVerts [4][i] = sideVerts [3][i - slices] | ||
else: | ||
sideVerts [4][i] = vertIndex | ||
vertIndex = vertIndex + 1 | ||
verts.append([0.0, 0.0, 0.0]) | ||
|
||
# Bottom | ||
for z in range(0, slices + 1): | ||
for x in range(0, slices + 1): | ||
i = x + z * lines | ||
if z == 0: | ||
sideVerts [5][i] = sideVerts [1][x] | ||
elif z == slices: | ||
sideVerts [5][i] = sideVerts [3][x] | ||
elif x == slices: | ||
sideVerts [5][i] = sideVerts [2][z] | ||
elif x == 0: | ||
sideVerts [5][i] = sideVerts [4][z] | ||
else: | ||
sideVerts [5][i] = vertIndex | ||
vertIndex = vertIndex + 1 | ||
verts.append([0.0, 0.0, 0.0]) | ||
|
||
faces = [] | ||
tempVerts = [] | ||
for s in range(0, 6): | ||
invertedWinding = (s == 3) or (s == 4) or (s == 5) | ||
for y in range(0, slices): | ||
for x in range(0, slices): | ||
i = x + y * lines | ||
tempVerts.clear() | ||
if invertedWinding: | ||
tempVerts.append(sideVerts[s][i]) | ||
tempVerts.append(sideVerts[s][i + 1]) | ||
tempVerts.append(sideVerts[s][i + 1 + lines]) | ||
tempVerts.append(sideVerts[s][i + lines]) | ||
else: | ||
tempVerts.append(sideVerts[s][i]) | ||
tempVerts.append(sideVerts[s][i + lines]) | ||
tempVerts.append(sideVerts[s][i + lines + 1]) | ||
tempVerts.append(sideVerts[s][i + 1]) | ||
faces.append(tuple(tempVerts)) | ||
|
||
sides = ((0, 1, 0), (0, 0, -1), (1, 0, 0), (0, 0, 1), (-1, 0, 0), (0, -1, 0)) | ||
xDirections = ((1, 0, 0), (1, 0, 0), (0, 0, 1), (1, 0, 0), (0, 0, 1), (1, 0, 0)) | ||
yDirections = ((0, 0, 1), (0, 1, 0), (0, 1, 0), (0, 1, 0), (0, 1, 0), (0, 0, 1)) | ||
quarterPi = pi / 4 | ||
for s in range(0, 6): | ||
side = sides[s] | ||
xDir = xDirections[s] | ||
yDir = yDirections[s] | ||
for y in range(0, slices + 1): | ||
for x in range(0, slices + 1): | ||
i = x + y * lines | ||
|
||
# Range -1 to 1. | ||
xLinear = (2 * x - slices) / slices | ||
yLinear = (2 * y - slices) / slices | ||
|
||
# More even distribution | ||
xLinear = tan(xLinear * quarterPi) | ||
yLinear = tan(yLinear * quarterPi) | ||
|
||
newPos = [side[i] + xLinear * xDir[i] + yLinear * yDir[i] for i in range(0, 3)] | ||
newPosLen = (newPos[0]*newPos[0] + newPos[1]*newPos[1] + newPos[2]*newPos[2]) ** (1/2) | ||
newPos = [newPos[i] / newPosLen for i in range(0, 3)] | ||
verts[sideVerts[s][i]][0] = newPos[0] | ||
verts[sideVerts[s][i]][1] = newPos[1] | ||
verts[sideVerts[s][i]][2] = newPos[2] | ||
|
||
return [(v[0] * size, v[1] * size, v[2] * size) for v in verts], faces | ||
|
||
class AddEvenQuadSphere(Operator, object_utils.AddObjectHelper): | ||
bl_idname = "mesh.primitive_even_quad_sphere_add" | ||
bl_label = "Add Even Quad Sphere" | ||
bl_description = ("Create an even quad sphere") | ||
bl_options = {"REGISTER", "UNDO", "PRESET"} | ||
|
||
slices: IntProperty(name="Slices", description="Amount of quads in each direction", default=8, min=1, soft_min=2, soft_max=128, max=512, step=1) | ||
size: FloatProperty(name="Size", description="Size multiplier", default=1.0, min=0.000001, soft_min=0.001, soft_max=4096, max=1000000000, step=1) | ||
change: BoolProperty(name="Change") | ||
|
||
def execute(self, context): | ||
# turn off 'Enter Edit Mode' | ||
use_enter_edit_mode = bpy.context.preferences.edit.use_enter_edit_mode | ||
bpy.context.preferences.edit.use_enter_edit_mode = False | ||
|
||
if bpy.context.mode == "OBJECT": | ||
if context.selected_objects != [] and context.active_object and \ | ||
(context.active_object.data is not None) and ('EvenQuadSphere' in context.active_object.data.keys()) and \ | ||
(self.change == True): | ||
obj = context.active_object | ||
oldmesh = obj.data | ||
oldmeshname = obj.data.name | ||
verts, faces = even_quad_sphere(self.slices, self.size) | ||
mesh = bpy.data.meshes.new('EvenQuadSphere') | ||
mesh.from_pydata(verts, [], faces) | ||
obj.data = mesh | ||
for material in oldmesh.materials: | ||
obj.data.materials.append(material) | ||
bpy.data.meshes.remove(oldmesh) | ||
obj.data.name = oldmeshname | ||
else: | ||
verts, faces = even_quad_sphere(self.slices, self.size) | ||
mesh = bpy.data.meshes.new('EvenQuadSphere') | ||
mesh.from_pydata(verts, [], faces) | ||
obj = object_utils.object_data_add(context, mesh, operator=self) | ||
|
||
obj.data["EvenQuadSphere"] = True | ||
obj.data["change"] = False | ||
for prm in EvenQuadSphereParameters(): | ||
obj.data[prm] = getattr(self, prm) | ||
|
||
if bpy.context.mode == "EDIT_MESH": | ||
active_object = context.active_object | ||
name_active_object = active_object.name | ||
bpy.ops.object.mode_set(mode='OBJECT') | ||
verts, faces = even_quad_sphere(self.slices, self.size) | ||
mesh = bpy.data.meshes.new('EvenQuadSphere') | ||
mesh.from_pydata(verts, [], faces) | ||
obj = object_utils.object_data_add(context, mesh, operator=self) | ||
obj.select_set(True) | ||
active_object.select_set(True) | ||
bpy.context.view_layer.objects.active = active_object | ||
bpy.ops.object.join() | ||
context.active_object.name = name_active_object | ||
bpy.ops.object.mode_set(mode='EDIT') | ||
|
||
if use_enter_edit_mode: | ||
bpy.ops.object.mode_set(mode = 'EDIT') | ||
|
||
# restore pre operator state | ||
bpy.context.preferences.edit.use_enter_edit_mode = use_enter_edit_mode | ||
|
||
return {'FINISHED'} | ||
|
||
def invoke(self, context, event): | ||
return self.execute(context) | ||
|
||
def draw(self, context): | ||
layout = self.layout | ||
|
||
layout.prop(self, 'slices') | ||
layout.column().prop(self, 'size', expand=True) | ||
|
||
if self.change == False: | ||
col = layout.column(align=True) | ||
col.prop(self, 'align', expand=True) | ||
col = layout.column(align=True) | ||
col.prop(self, 'location', expand=True) | ||
col = layout.column(align=True) | ||
col.prop(self, 'rotation', expand=True) | ||
|
||
def EvenQuadSphereParameters(): | ||
EvenQuadSphereParameters = [ | ||
"slices", | ||
"size", | ||
] | ||
return EvenQuadSphereParameters |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
# This file must be run from the repository root. | ||
|
||
projectName = "even_quad_sphere" | ||
additionalDirectories = [] | ||
additionalFiles = ["LICENSE"] | ||
|
||
def getVersion(): | ||
import re | ||
with open("__init__.py", mode="r") as f: | ||
m = re.compile(r'^\s*"version"\s*:\s*\(\s*(?P<major>\d+)\s*,\s*(?P<minor>\d+)\s*(,\s*(?P<patch>\d+)\s*)?\)\s*,?\s*$', re.MULTILINE).search(f.read()) | ||
if m.group("patch") != None: | ||
return f'{m.group("major")}.{m.group("minor")}.{m.group("patch")}' | ||
else: | ||
return f'{m.group("major")}.{m.group("minor")}' | ||
|
||
if __name__ == "__main__": | ||
import zipfile | ||
from pathlib import Path | ||
zipFileName = f"{projectName}-{getVersion()}.zip" | ||
print(f"Writing {zipFileName}...") | ||
with zipfile.ZipFile(zipFileName, "w", zipfile.ZIP_DEFLATED) as zf: | ||
for dir in additionalDirectories: | ||
for path in Path(dir).rglob("*"): | ||
arcName = f"{projectName}/{path.as_posix()}" | ||
print(f"Adding {path} as {arcName}") | ||
zf.write(path, arcName) | ||
for path in Path(".").glob("*.py"): | ||
arcName = f"{projectName}/{path.as_posix()}" | ||
print(f"Adding {path} as {arcName}") | ||
zf.write(path, arcName) | ||
for fileName in additionalFiles: | ||
arcName = f"{projectName}/{fileName}" | ||
print(f"Adding {fileName} as {arcName}") | ||
zf.write(fileName, arcName) |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.