require 'modown/version'
require 'modown/options'
require 'net/http'
require 'nokogiri'
require 'zip'
# The main module.
module Modown
# The CLI is a class responsible of handling all the command line interface
# logic.
class CLI
def initialize
@options = {}
end
# This method is the entry point for the command-line app
def run(args = ARGV)
@options = Options.new.parse(args)
get_models(@options[:search_term], @options[:count], @options[:format])
end
# This method integrates the {download_model} , {search_models} and {get_model_from_zip} methods
#
# @param name [String] the name of the thing you want to download 3D models.example "cat","bottle",etc
# @param count [Integer] the number of models you want
# @param format [String] the glob pattern of the desired 3D model file format
# @return [void]
def get_models(name, count = 1, format = '*.3[Dd][Ss]')
Modown::search_models(name, count).each do |id|
Modown::download_model(id)
Modown::get_model_from_zip(id + '.zip', name + '_' + id, format)
end
end
end
# Downloads a file at a given url and writes it to disk.
# Taken from - https://gist.github.com/Burgestrand/454926
# @param url [URL] the url of the file to download
# @param save_as [string] the name/path of the file to save, along with the extension
# @return [void]
def self.download(url, save_as)
Thread.new do
thread = Thread.current
url = URI.parse(url)
Net::HTTP.new(url.host, url.port).request_get(url.path) do |response|
thread[:length] = response['Content-Length'].to_i
puts " #{(thread[:length] / 1_000_000.0)} MB "
open(save_as, 'wb') do |file|
response.read_body do |fragment|
file.write(fragment)
thread[:done] = (thread[:done] || 0) + fragment.length
thread[:progress] = thread[:done].quo(thread[:length]) * 100
end
end
end
end
end
# Downloads the model from archive3D and saves it in a zip file.
#
# @param model_id [String] The `id` of the model to download
# @param save_to [String] Where to save the file.
# @return [void]
def self.download_model(model_id,save_to="")
puts 'Please wait downloading Model'
download_url = 'http://www.archive3d.net/?a=download&do=get&id='
begin
response = Net::HTTP.get_response(URI.parse(download_url + model_id))
file_location = response['location']
thread = download(file_location, save_to + model_id + '.zip')
print "#{thread[:progress].to_i}% \r" until thread.join 1
rescue
puts "Can't download model", $!
0
else
puts model_id + ' downloaded !'
1
end
end
# The zip file of the downloaded model contains different 3D formats
# This method extracts a file with the given format from the zip file.
# More specifically, it gets the files which matches the format (glob pattern) and extracts them
# and saves it with the name output_file and their respective format
#
# @param input_zip [String] the name of the zip file
# @param output_file [String] the desired name of the model file.
# @param format [String] glob pattern for any of the 3D file formats ( for example ".3[Dd][Ss]")
# @return [void]
def self.get_model_from_zip(input_zip, output_file, format = '*')
Zip::File.open(input_zip) do |zip_file|
matches = zip_file.glob(format)
matches.each do |entry|
begin
file_complete_name = entry.name.split('.')
file_name = file_complete_name[0]
extension = file_complete_name[1..-1].join('.').downcase
entry.extract(output_file + "_" + file_name + '.' + extension)
rescue Zip::DestinationFileExistsError
end
end
end
1
rescue
puts 'Error opening ZIP file'
0
end
# This methods searches for 3D models on archive3D.net and returns a list of model_ids
#
# @param name [String] something you want to search , for example "dog", "table", etc
# @param count [Integer] the number of models you want. The number of actual models returned are always <= count
# @return [Array] the list of the model_ids
def self.search_models(name, count = 1)
id_list = []
search_url = 'http://www.archive3d.net/?tag='
begin
page = Nokogiri::HTML(Net::HTTP.get_response(URI.parse(search_url + name)).body)
rescue
puts 'There was problem contacting archive3d', $!
else
count.times do |x|
x += 1
begin
element = page.css("#prevtable > div:nth-child(#{x}) > div > a")[0]
model_id = element['href'].split('id=')[1]
rescue
else
id_list << model_id
end
end
end
puts "Found #{id_list.size} models ! \n"
id_list
end
end