Wavefront OBJ exporter
Forum rules
Be nice to others! Respect the FreeCAD code of conduct!
Be nice to others! Respect the FreeCAD code of conduct!
- vejmarie
- Posts: 713
- Joined: Mon Jan 04, 2016 4:52 pm
- Location: Somewhere between France, USA and Taiwan
- Contact:
Wavefront OBJ exporter
Hello,
I am playing a little with xeogl stuff which is a javascript object viewer in webgl and that support wavefront obj as input. I struggled at displaying properly our model despite the export of the material file. After some time digging it looks like that this is coming from the lack of normal vector export within the file (vn) for each surface contained into the OBJ file. Is this something that somebody would like to have a look to ? I had a quick look at the python code written by Yorik, but I didn't find an easy way to export or compute the normal into it. I believe it needs to be done for each triangles when the Shape is tesselated. I could get inspired by the Mesh module either into Fem or the Mesh workbench but if somebody has a better idea feel free to suggest !
vejmarie
I am playing a little with xeogl stuff which is a javascript object viewer in webgl and that support wavefront obj as input. I struggled at displaying properly our model despite the export of the material file. After some time digging it looks like that this is coming from the lack of normal vector export within the file (vn) for each surface contained into the OBJ file. Is this something that somebody would like to have a look to ? I had a quick look at the python code written by Yorik, but I didn't find an easy way to export or compute the normal into it. I believe it needs to be done for each triangles when the Shape is tesselated. I could get inspired by the Mesh module either into Fem or the Mesh workbench but if somebody has a better idea feel free to suggest !
vejmarie
- adrianinsaval
- Veteran
- Posts: 5551
- Joined: Thu Apr 05, 2018 5:15 pm
Re: Wavefront OBJ exporter
If somone is going to do it maybe he could take a look at this too? https://forum.freecadweb.org/viewtopic. ... 18#p338318
Re: Wavefront OBJ exporter
As I see from Arch/importOBJ.py, the exporter only exports lists of vertices, edges, and faces. You are probably right, it doesn't consider face normals.
It seems that the code just tessellates the Shape, withIs this something that somebody would like to have a look to ? I had a quick look at the python code written by Yorik, but I didn't find an easy way to export or compute the normal into it. I believe it needs to be done for each triangles when the Shape is tesselated.
Code: Select all
tris = shape.Face.tessellate(1)
The tessellate command comes from the Part module. Maybe there is an OpenCASCADE function in Part which provides the normal as well.
Lately Nocturnial wanted to improve the OBJ exporter as well, in order to correctly export multi-materials. I've never seen the Wavefront OBJ standard, so I am not sure what is supposed to support. If the standard mentions face normals, then we should support them of course. By reading Nocturnial's post, it gave me the impression that the OBJ exporter was written by Yorik mostly with the intention of producing OBJs that are are compatible with Blender. Maybe he didn't add more functionality, just the basics.
Nocturnial wrote: ↑Wed Sep 04, 2019 6:36 pm I'm glad you brought this up because it forced me to reread the standard. I'm using version 4.2 and couldn't find anything more recent online and offline. The change from transparency "Tr" to dissolve "d" is correct. Transparency "Tr" isn't in the standard but dissolve is. From what I gathered is that Tr = 1.0 - d. I think "Tr" is an extension introduced by some programs. However I might do a better job in selecting the illumination model "illum".
...
It's nitpicking because I "technically" follow the standard but not the spirit of the standard and probably only affects real-time engines.
...
I think the authors of the blender wavefront importer made the same mistake as I initially did. ... I'll submit a patch to blender and see what they say about this.
Always add the important information to your posts if you need help. Also see Tutorials and Video tutorials.
To support the documentation effort, and code development, your donation is appreciated: liberapay.com/FreeCAD.
To support the documentation effort, and code development, your donation is appreciated: liberapay.com/FreeCAD.
Re: Wavefront OBJ exporter
I currently use this code in the render workbench code.
This code uses meshes as input and uses the normals per face. If you want a smoothed object you need to extract the normals per point:instead of per face
Ideally we would use a crease angle, but that's a lot more work. (see https://github.com/FreeCAD/FreeCAD-render/issues/11)
You can remove the parts where I write the texture coordinates if you don't need them. Otherwise coordinate with furti, you need to install a specific branch of archtexture.
To convert a shape to a mesh you can use the following code (instead of using tesselate):
It has a lot more options than tesselate and you get the normals for free. I got the parameters I used from working on yoriks render workbench, but I would recommend setting relative to true. Otherwise a very large curved surface get tesselated into a lot of small tris without adding much detail. An obj file of a couple kb (Relative=True) became 22Mb (Relative=False). In any case, experiment a bit with the parameters until you have something you like. Check the help for meshFromShape for all possible parameters.
This code uses meshes as input and uses the normals per face. If you want a smoothed object you need to extract the normals per point:
Code: Select all
mesh.Point[i].Normal
Code: Select all
mesh.Facets[i].Normal
You can remove the parts where I write the texture coordinates if you don't need them. Otherwise coordinate with furti, you need to install a specific branch of archtexture.
To convert a shape to a mesh you can use the following code (instead of using tesselate):
Code: Select all
import MeshPart
MeshPart.meshFromShape(Shape=shape, LinearDeflection=0.1, AngularDeflection=0.523599, Relative=False)
Re: Wavefront OBJ exporter
By the way, did you do anything else with the Wavefront OBJ exporter? Or did you decide to focus on the Render workbench instead?
Always add the important information to your posts if you need help. Also see Tutorials and Video tutorials.
To support the documentation effort, and code development, your donation is appreciated: liberapay.com/FreeCAD.
To support the documentation effort, and code development, your donation is appreciated: liberapay.com/FreeCAD.
- vejmarie
- Posts: 713
- Joined: Mon Jan 04, 2016 4:52 pm
- Location: Somewhere between France, USA and Taiwan
- Contact:
Re: Wavefront OBJ exporter
Thanks for all your feedback. We definitely need the normal into the wavefront to properly render the colors etc ... It is supported by the file format which in the end is a good option to share multi meshes and render in webgl lately. I will pursue some work there (I a not a python guy at all, but will learn).
- vejmarie
- Posts: 713
- Joined: Mon Jan 04, 2016 4:52 pm
- Location: Somewhere between France, USA and Taiwan
- Contact:
Re: Wavefront OBJ exporter
Got a small fix to Yorik writer based on your very useful feedback (it saved me a lot of time ). Will run for a PR later today or early this week
- vejmarie
- Posts: 713
- Joined: Mon Jan 04, 2016 4:52 pm
- Location: Somewhere between France, USA and Taiwan
- Contact:
Re: Wavefront OBJ exporter
Got it working. Still need to find a way to fix the placement issue. I am trying to export an assembly everything is inside with the relevant normal vector now, but the placement is broken (and seriously). I am trying to convert in fact big step files to obj ones
Re: Wavefront OBJ exporter
I don't know how your code looks like but when applying a placement to the normal vectors you must only apply the rotational part but not the translational part.Got it working. Still need to find a way to fix the placement issue. I am trying to export an assembly everything is inside with the relevant normal vector now, but the placement is broken (and seriously). I am trying to convert in fact big step files to obj ones
Let's consider the placement P with rotation R and translation t and the points p1 and p2 and the vector v = p2-p1. If you apply the placement to p1 and p2 you get:
p1' = R * p1 + t
p2' = R * p2 + t
v' will be then p2' - p1' = (R * p2 + t) - (R * p1 + t) = R * p2 - R * p1 = R * (p2 - p1) = R * v
As you can see t disappears for the vector which makes sense because a vector is position-invariant.
- vejmarie
- Posts: 713
- Joined: Mon Jan 04, 2016 4:52 pm
- Location: Somewhere between France, USA and Taiwan
- Contact:
Re: Wavefront OBJ exporter
Hi, thanks for the feedback. I was expecting to apply rotation only. I am facing in the end the same issue than https://forum.freecadweb.org/viewtopic.php?t=16545. The proposed solution is not fully ready to be implemented within Yorik code.wmayer wrote: ↑Mon Oct 07, 2019 8:26 amI don't know how your code looks like but when applying a placement to the normal vectors you must only apply the rotational part but not the translational part.Got it working. Still need to find a way to fix the placement issue. I am trying to export an assembly everything is inside with the relevant normal vector now, but the placement is broken (and seriously). I am trying to convert in fact big step files to obj ones
Let's consider the placement P with rotation R and translation t and the points p1 and p2 and the vector v = p2-p1. If you apply the placement to p1 and p2 you get:
p1' = R * p1 + t
p2' = R * p2 + t
v' will be then p2' - p1' = (R * p2 + t) - (R * p1 + t) = R * p2 - R * p1 = R * (p2 - p1) = R * v
As you can see t disappears for the vector which makes sense because a vector is position-invariant.
Here is the update I made
Code: Select all
def getIndices(shape,offsetv,offsetvn):
"returns a list with 2 lists: vertices and face indexes, offsetted with the given amount"
vlist = []
vnlist = []
elist = []
flist = []
curves = None
if isinstance(shape,Part.Shape):
for e in shape.Edges:
try:
if not isinstance(e.Curve,Part.LineSegment):
if not curves:
mesh=MeshPart.meshFromShape(Shape=shape, LinearDeflection=0.1, AngularDeflection=0.523599, Relative=False)
FreeCAD.Console.PrintWarning(translate("Arch","Found a shape containing curves, triangulating")+"\n")
break
except: # unimplemented curve type
mesh=MeshPart.meshFromShape(Shape=shape, LinearDeflection=0.1, AngularDeflection=0.523599, Relative=False)
FreeCAD.Console.PrintWarning(translate("Arch","Found a shape containing curves, triangulating")+"\n")
break
elif isinstance(shape,Mesh.Mesh):
curves = shape.Topology
if mesh:
for v in mesh.Topology[0]:
vlist.append(" "+str(round(v[0],p))+" "+str(round(v[1],p))+" "+str(round(v[2],p)))
for vn in mesh.Facets:
vnlist.append(" "+str(vn.Normal[0]) + " " + str(vn.Normal[1]) + " " + str(vn.Normal[2]))
for i, vn in enumerate(mesh.Topology[1]):
flist.append(" "+str(vn[0]+offsetv)+"//"+str(i+offsetvn)+" "+str(vn[1]+offsetv)+"//"+str(i+offsetvn)+" "+str(vn[2]+offsetv)+"//"+str(i+offsetvn)+" ")
else:
print("using original code")
if curves:
for v in curves[0]:
vlist.append(" "+str(round(v.x,p))+" "+str(round(v.y,p))+" "+str(round(v.z,p)))
for f in curves[1]:
fi = ""
for vi in f:
fi += " " + str(vi + offsetv)
flist.append(fi)
else:
for v in shape.Vertexes:
vlist.append(" "+str(round(v.X,p))+" "+str(round(v.Y,p))+" "+str(round(v.Z,p)))
if not shape.Faces:
for e in shape.Edges:
if DraftGeomUtils.geomType(e) == "Line":
ei = " " + str(findVert(e.Vertexes[0],shape.Vertexes) + offsetv)
ei += " " + str(findVert(e.Vertexes[-1],shape.Vertexes) + offsetv)
elist.append(ei)
for f in shape.Faces:
if len(f.Wires) > 1:
# if we have holes, we triangulate
tris = f.tessellate(1)
for fdata in tris[1]:
fi = ""
for vi in fdata:
vdata = Part.Vertex(tris[0][vi])
fi += " " + str(findVert(vdata,shape.Vertexes) + offsetv)
flist.append(fi)
else:
fi = ""
for e in f.OuterWire.OrderedEdges:
#print(e.Vertexes[0].Point,e.Vertexes[1].Point)
v = e.Vertexes[0]
ind = findVert(v,shape.Vertexes)
if ind == None:
return None,None,None
fi += " " + str(ind + offsetv)
flist.append(fi)
return vlist,vnlist,elist,flist
Last edited by vejmarie on Mon Oct 07, 2019 12:45 pm, edited 1 time in total.