-2

Problem

How can modern Fortran navigate a file with double forward slashes between entries, as in the *.obj format? The goal is to extract the vertex index (first entry) and ignore the vertex normal index (second entry).

Example

For example, for this snippet,

f 297//763 298//763 296//763
f 296//764 298//764 295//764
f 384//765 385//765 382//765
f 384//766 382//766 383//766

the goal is to create an array like this:

face ( 1 ) = [297, 298, 296]
face ( 2 ) = [296, 298, 295]
face ( 3 ) = [384, 385, 382]
face ( 4 ) = [384, 382, 383]

Extra points for an answer which would adopt to a richer format like

f a//b//c  d//e//f g//h//i j//k//l

Other posts

The answer for [How to get Wavefront .obj file with 3 faces (traingle) points to a deleted blog. This post [How to read numeric data from a string in FORTRAN was not relevant.

References

Three references on the *.obj format: Object Files (.obj), B1. Object Files (.obj), Wavefront .obj file

dantopa
  • 616
  • 7
  • 19
  • 1
    This looks like a homework problem. What have you tried? – evets Apr 04 '20 at 05:58
  • Step 1 - use `sed` (or another similar tool) to preprocess the file to replace `//` with spaces. Step 2 - use Fortran's *list-directed* input capabilities to read a line with one character and 6 integers (v easy). Step 3 - slot data into the `face` array as you wish. – High Performance Mark Apr 04 '20 at 08:42
  • @evets: Not a homework problem. The task is to harvest *.obj files from CAD routines to extract surfaces for input to other analysis routines. – dantopa Apr 04 '20 at 17:22
  • @High Performance Mark: Yes, sed and awk (and others will work), but the goal is to deliver a single Fortran executable. Please note the task is to take the leading terms, not all terms (3 integers vs. 6). – dantopa Apr 04 '20 at 17:26
  • 2
    Well, what have your tried? Opening the file with `access=stream` allows you to read the file a character at a time, which allows you to write a parser to do whatever you want. Simply opening the file and reading with `write(*,'(A)')` will read an entire line. You can then use `INDEX` to scan for `/` and peak at the next character to find `'//'`. Fairly trivial stuff. – evets Apr 04 '20 at 18:47

1 Answers1

2

As suggested in the comments, we can replace / by a space using sed etc. We can also scan each character one by one in Fortran (see below). We then read in all integers into vals and select the desired part as vals( 1:6:2 ). A similar approach can be used for f a//b//c d//e//f ... etc by changing 1:6:2 to 1:12:3 etc.

[test.f90]
program main
    implicit none
    character(100) buf
    integer vals(10), ios, i

    open(10, file="test.dat", status="old")
    do
        read(10, "(a)", iostat=ios) buf
        if (ios /= 0) exit

        do i = 1, len(buf)
            if (buf(i:i) == "/") buf(i:i) = " "   !! replace "/" by " "
        enddo

        read(buf(2:), *) vals( 1:6 )  !! read all items
        print *, vals( 1:6:2 )  !! select items 1, 3, 5
    enddo
    close(10)
end

[test.dat]
f 297//763 298//763 296//763
f 296//764 298//764 295//764
f 384//765 385//765 382//765
f 384//766 382//766 383//766

$ gfortran test.f90 && ./a.out
     297         298         296
     296         298         295
     384         385         382
     384         382         383

Just for fun, here is a similar code in Python, which is shorter thanks to replace() and split(). If we have some similar routines, I guess the above code may also become shorter.

dat = []
for line in open("test.dat").readlines():
    dat.append( line[1:] .replace("/", " ") .split() [::2] )

import numpy as np
face = np.array(dat, dtype=int)
print(face)

$ python test.py
[[297 298 296]
 [296 298 295]
 [384 385 382]
 [384 382 383]]
roygvib
  • 7,218
  • 2
  • 19
  • 36
  • I initially used "merge" for replacing characters, but I changed it to a simple "if" because it was simpler in this case... – roygvib Apr 05 '20 at 02:42