Arch: added 3DS importer

This commit is contained in:
Yorik van Havre 2016-05-29 00:10:42 -03:00
parent 14d724d722
commit 62cb403125
6 changed files with 2303 additions and 0 deletions

View File

@ -35,6 +35,13 @@ SET(Arch_SRCS
ArchMaterial.py
ArchSchedule.py
ArchProfile.py
import3DS.py
)
SET(Dice3DS_SRCS
Dice3DS/__init__.py
Dice3DS/util.py
Dice3DS/dom3ds.py
)
SET(Arch_presets
@ -48,6 +55,7 @@ ADD_CUSTOM_TARGET(Arch ALL
)
fc_copy_sources(Arch "${CMAKE_BINARY_DIR}/Mod/Arch" ${Arch_SRCS})
fc_copy_sources(Arch "${CMAKE_BINARY_DIR}/Mod/Arch/Dice3DS" ${Dice3DS_SRCS})
fc_target_copy_resource(Arch
${CMAKE_SOURCE_DIR}/src/Mod/Arch
@ -69,6 +77,12 @@ INSTALL(
DESTINATION Mod/Arch
)
INSTALL(
FILES
${Dice3DS_SRCS}
DESTINATION Mod/Arch/Dice3DS
)
INSTALL(
DIRECTORY
Presets

View File

@ -0,0 +1,3 @@
# __init__.py
__all__ = [ 'dom3ds', 'util' ]
version = (0,13)

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,272 @@
# util.py
"""Utitily function for Dice3DS.
Defines some routines for calculating normals and transforming points.
"""
import numpy
# Can push numpy.float64 (or even numpy.float80) into this if you
# would like to use higher precision when calculating; results will be
# converted back to numpy.float32
_calc_precision_type = numpy.float32
def translate_points(pointarray,matrix):
"""Translate points in pointarray by the given matrix.
tpointarray = translate_points(pointarray,matrix)
Takes array of points and a homogenous (4D) transformation
matrix in exactly the same form in which they appear in the
3DS DOM.
Returns a pointarray with the points transformed by the matrix.
"""
n = len(pointarray)
pt = numpy.ones((n,4),_calc_precision_type)
pt[:,:3] = pointarray
tpt = numpy.transpose(numpy.dot(matrix,numpy.transpose(pt)))
return numpy.asarray(tpt[:,:3]/tpt[:,3:4],numpy.float32)
def calculate_normals_no_smoothing(pointarray,facearray,smarray=None):
"""Calculate normals all perpendicular to the faces.
points,norms = calculate_normals_no_smoothing(
pointarray,facearray,smarray=None)
Takes an array of points and faces in exactly the same form in
which they appear in the 3DS DOM. It accepts a smoothing array,
but ignores it.
Returns a numpy.array of points, one per row, and a
numpy.array of the corresponding normals. The points are
returned as a list of consecutive triangles; the first three rows
make up the first triangle, the second three rows make up the
second triangle, and so on.
The normal vectors are determined by calculating the normal to
each face. There is no smoothing.
"""
# prepare to calculate normals. define some arrays
m = len(facearray)
fnorms = numpy.empty((m*3,3),_calc_precision_type)
points = pointarray[facearray.ravel()]
# calculate normals for each face
A = numpy.asarray(pointarray[facearray[:,0]],_calc_precision_type)
B = numpy.asarray(pointarray[facearray[:,1]],_calc_precision_type)
C = numpy.asarray(pointarray[facearray[:,2]],_calc_precision_type)
b = A - C
c = B - A
fnorms[2::3,0] = c[:,2]*b[:,1]-c[:,1]*b[:,2]
fnorms[2::3,1] = c[:,0]*b[:,2]-c[:,2]*b[:,0]
fnorms[2::3,2] = c[:,1]*b[:,0]-c[:,0]*b[:,1]
a = fnorms[2::3]
q = numpy.maximum(numpy.sqrt(numpy.sum(a*a,axis=1)),1e-10)
q = q[:,numpy.newaxis]
a /= q
fnorms[0::3] = fnorms[1::3] = fnorms[2::3]
# we're done
return points, numpy.asarray(fnorms,numpy.float32)
def calculate_normals_by_cross_product(pointarray,facearray,smarray):
"""Calculate normals by smoothing, weighting by cross-product.
points,norms = calculate_normals_by_cross_product(
pointarray,facearray,smarray)
Takes an array of points, faces, and a smoothing group in exactly
the same form in which they appear in the 3DS DOM.
Returns a numpy.array of points, one per row, and a numpy.array of
the corresponding normals. The points are returned as a list of
consecutive triangles; the first three rows make up the first
triangle, the second three rows make up the second triangle, and
so on.
To calculate the normal of a given vertex on a given face, this
function averages the normal vector for all faces which have share
that vertex and a smoothing group.
The normals being averaged are weighted by the cross-product used
to obtain the face's normal, which is proportional to the area of
the face.
"""
# prepare to calculate normals. define some arrays
m = len(facearray)
rnorms = numpy.zeros((m*3,3),_calc_precision_type)
fnorms = numpy.zeros((m*3,3),_calc_precision_type)
points = pointarray[facearray.ravel()]
exarray = numpy.zeros(3*m,numpy.uint32)
if smarray is not None:
exarray[0::3] = exarray[1::3] = exarray[2::3] = smarray
# calculate scaled normals (according to angle subtended)
A = numpy.asarray(pointarray[facearray[:,0]],_calc_precision_type)
B = numpy.asarray(pointarray[facearray[:,1]],_calc_precision_type)
C = numpy.asarray(pointarray[facearray[:,2]],_calc_precision_type)
a = C - B
b = A - C
c = B - A
rnorms[0::3,0] = c[:,2]*b[:,1]-c[:,1]*b[:,2]
rnorms[0::3,1] = c[:,0]*b[:,2]-c[:,2]*b[:,0]
rnorms[0::3,2] = c[:,1]*b[:,0]-c[:,0]*b[:,1]
rnorms[1::3,0] = a[:,2]*c[:,1]-a[:,1]*c[:,2]
rnorms[1::3,1] = a[:,0]*c[:,2]-a[:,2]*c[:,0]
rnorms[1::3,2] = a[:,1]*c[:,0]-a[:,0]*c[:,1]
rnorms[2::3,0] = b[:,2]*a[:,1]-b[:,1]*a[:,2]
rnorms[2::3,1] = b[:,0]*a[:,2]-b[:,2]*a[:,0]
rnorms[2::3,2] = b[:,1]*a[:,0]-b[:,0]*a[:,1]
# normalize vectors according to passed in smoothing group
lex = numpy.lexsort(numpy.transpose(points))
brs = numpy.nonzero(
numpy.any(points[lex[1:],:]-points[lex[:-1],:],axis=1))[0]+1
lslice = numpy.empty((len(brs)+1,),numpy.int)
lslice[0] = 0
lslice[1:] = brs
rslice = numpy.empty((len(brs)+1,),numpy.int)
rslice[:-1] = brs
rslice[-1] = 3*m
for i in xrange(len(brs)+1):
rgroup = lex[lslice[i]:rslice[i]]
xgroup = exarray[rgroup]
normpat = numpy.logical_or(
numpy.bitwise_and.outer(xgroup,xgroup),
numpy.eye(len(xgroup)))
fnorms[rgroup,:] = numpy.dot(normpat,rnorms[rgroup,:])
q = numpy.sum(fnorms*fnorms,axis=1)
qnz = numpy.nonzero(q)[0]
lq = 1.0 / numpy.sqrt(q[qnz])
fnt = numpy.transpose(fnorms)
fnt[:,qnz] *= lq
# we're done
return points, numpy.asarray(fnorms,numpy.float32)
def calculate_normals_by_angle_subtended(pointarray,facearray,smarray):
"""Calculate normals by smoothing, weighting by angle subtended.
points,norms = calculate_normals_by_angle_subtended(
pointarray,facearray,smarray)
Takes an array of points, faces, and a smoothing group in exactly
the same form in which they appear in the 3DS DOM.
Returns a numpy.array of points, one per row, and a numpy.array of
the corresponding normals. The points are returned as a list of
consecutive triangles; the first three rows make up the first
triangle, the second three rows make up the second triangle, and
so on.
To calculate the normal of a given vertex on a given face, this
function averages the normal vector for all faces which have share
that vertex, and a smoothing group.
The normals being averaged are weighted by the angle subtended.
"""
# prepare to calculate normals. define some arrays
m = len(facearray)
rnorms = numpy.zeros((m*3,3),_calc_precision_type)
fnorms = numpy.zeros((m*3,3),_calc_precision_type)
points = pointarray[facearray.ravel()]
exarray = numpy.zeros(3*m,numpy.uint32)
if smarray is not None:
exarray[0::3] = exarray[1::3] = exarray[2::3] = smarray
# weed out degenerate triangles first
# unlike cross-product, angle subtended blows up on degeneracy
A = numpy.asarray(pointarray[facearray[:,0]],_calc_precision_type)
B = numpy.asarray(pointarray[facearray[:,1]],_calc_precision_type)
C = numpy.asarray(pointarray[facearray[:,2]],_calc_precision_type)
a = C - B
b = A - C
c = B - A
p = numpy.zeros((len(facearray),3),_calc_precision_type)
p[:,0] = c[:,2]*b[:,1]-c[:,1]*b[:,2]
p[:,1] = c[:,0]*b[:,2]-c[:,2]*b[:,0]
p[:,2] = c[:,1]*b[:,0]-c[:,0]*b[:,1]
aa = numpy.sum(a*a,axis=1)
bb = numpy.sum(b*b,axis=1)
cc = numpy.sum(c*c,axis=1)
pp = numpy.sum(p*p,axis=1)
ndg = numpy.nonzero(numpy.logical_and.reduce((aa,bb,cc,pp)))[0]
# calculate scaled normals (according to angle subtended)
p = p[ndg]
la = numpy.sqrt(aa[ndg])
lb = numpy.sqrt(bb[ndg])
lc = numpy.sqrt(cc[ndg])
lp = numpy.sqrt(pp[ndg])
sinA = numpy.clip(lp/lb/lc,-1.0,1.0)
sinB = numpy.clip(lp/la/lc,-1.0,1.0)
sinC = numpy.clip(lp/la/lb,-1.0,1.0)
sinA2 = sinA*sinA
sinB2 = sinB*sinB
sinC2 = sinC*sinC
angA = numpy.arcsin(sinA)
angB = numpy.arcsin(sinB)
angC = numpy.arcsin(sinC)
angA = numpy.where(sinA2 > sinB2 + sinC2, numpy.pi - angA, angA)
angB = numpy.where(sinB2 > sinA2 + sinC2, numpy.pi - angB, angB)
angC = numpy.where(sinC2 > sinA2 + sinB2, numpy.pi - angC, angC)
rnorms[0::3][ndg] = p*(angA/lp)[:,numpy.newaxis]
rnorms[1::3][ndg] = p*(angB/lp)[:,numpy.newaxis]
rnorms[2::3][ndg] = p*(angC/lp)[:,numpy.newaxis]
# normalize vectors according to passed in smoothing group
lex = numpy.lexsort(numpy.transpose(points))
brs = numpy.nonzero(
numpy.any(points[lex[1:],:]-points[lex[:-1],:],axis=1))[0]+1
lslice = numpy.empty((len(brs)+1,),numpy.int)
lslice[0] = 0
lslice[1:] = brs
rslice = numpy.empty((len(brs)+1,),numpy.int)
rslice[:-1] = brs
rslice[-1] = 3*m
for i in xrange(len(brs)+1):
rgroup = lex[lslice[i]:rslice[i]]
xgroup = exarray[rgroup]
normpat = numpy.logical_or(
numpy.bitwise_and.outer(xgroup,xgroup),
numpy.eye(len(xgroup)))
fnorms[rgroup,:] = numpy.dot(normpat,rnorms[rgroup,:])
q = numpy.sum(fnorms*fnorms,axis=1)
qnz = numpy.nonzero(q)[0]
lq = 1.0 / numpy.sqrt(q[qnz])
fnt = numpy.transpose(fnorms)
fnt[:,qnz] *= lq
# we're done
return points, numpy.asarray(fnorms,numpy.float32)

View File

@ -28,3 +28,4 @@ FreeCAD.addExportType("Wavefront OBJ - Arch module (*.obj)","importOBJ")
FreeCAD.addExportType("WebGL file (*.html)","importWebGL")
FreeCAD.addImportType("Collada (*.dae)","importDAE")
FreeCAD.addExportType("Collada (*.dae)","importDAE")
FreeCAD.addImportType("3D Studio mesh (*.3ds)","import3DS")

105
src/Mod/Arch/import3DS.py Normal file
View File

@ -0,0 +1,105 @@
#***************************************************************************
#* *
#* Copyright (c) 2016 Yorik van Havre <yorik@uncreated.net> *
#* *
#* This program is free software; you can redistribute it and/or modify *
#* it under the terms of the GNU Lesser General Public License (LGPL) *
#* as published by the Free Software Foundation; either version 2 of *
#* the License, or (at your option) any later version. *
#* for detail see the LICENCE text file. *
#* *
#* This program is distributed in the hope that it will be useful, *
#* but WITHOUT ANY WARRANTY; without even the implied warranty of *
#* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
#* GNU Library General Public License for more details. *
#* *
#* You should have received a copy of the GNU Library General Public *
#* License along with this program; if not, write to the Free Software *
#* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
#* USA *
#* *
#***************************************************************************
import os,FreeCAD,Mesh
__title__="FreeCAD 3DS importer"
__author__ = "Yorik van Havre"
__url__ = "http://www.freecadweb.org"
DEBUG = True
def check3DS():
"checks if collada if available"
global dom3ds
dom3ds = None
try:
from Dice3DS import dom3ds
except ImportError:
FreeCAD.Console.PrintError("Dice3DS not found, 3DS support is disabled.\n")
return False
else:
return True
def open(filename):
"called when freecad wants to open a file"
if not check3DS():
return
docname = (os.path.splitext(os.path.basename(filename))[0]).encode("utf8")
doc = FreeCAD.newDocument(docname)
doc.Label = decode(docname)
FreeCAD.ActiveDocument = doc
read(filename)
return doc
def insert(filename,docname):
"called when freecad wants to import a file"
if not check3DS():
return
try:
doc = FreeCAD.getDocument(docname)
except NameError:
doc = FreeCAD.newDocument(docname)
FreeCAD.ActiveDocument = doc
read(filename)
return doc
def decode(name):
"decodes encoded strings"
try:
decodedName = (name.decode("utf8"))
except UnicodeDecodeError:
try:
decodedName = (name.decode("latin1"))
except UnicodeDecodeError:
FreeCAD.Console.PrintError(translate("Arch","Error: Couldn't determine character encoding"))
decodedName = name
return decodedName
def read(filename):
dom = dom3ds.read_3ds_file(filename,tight=False)
for j,d_nobj in enumerate(dom.mdata.objects):
if type(d_nobj.obj) != dom3ds.N_TRI_OBJECT:
continue
verts = []
if d_nobj.obj.points:
for d_point in d_nobj.obj.points.array:
verts.append([d_point[0],d_point[1],d_point[2]])
meshdata = []
for d_face in d_nobj.obj.faces.array:
meshdata.append([verts[int(d_face[i])] for i in xrange(3)])
m = [tuple(r) for r in d_nobj.obj.matrix.array]
m = m[0] + m[1] + m[2] + m[3]
placement = FreeCAD.Placement(FreeCAD.Matrix(*m))
mesh = Mesh.Mesh(meshdata)
obj = FreeCAD.ActiveDocument.addObject("Mesh::Feature","Mesh")
obj.Mesh = mesh
obj.Placement = placement
else:
print "Skipping object without vertices array: ",d_nobj.obj