Thursday, February 28, 2019

擦亮年輕人的網際內容名牌

這裡所謂年輕人的網際內容名牌, 指的是 Github 帳號, 網誌與 https://www.linkedin.com/.

Github 帳號可以呈現所執行專案的歷程細節資料, 而網誌可以是放在 Github Pages 上的靜態網誌, 也可以是 Google Blogger 等動態網誌, 表現的是個人在學習過程或執行專案歷程, 所逐一發表的重點註記、心得或觀點.

而 https://www.linkedin.com/ 則可視為您的專業履歷表.

Tuesday, February 26, 2019

Github Classroom

這個學期 KMOL 仍然沿用 https://classroom.github.com/, 並且將以 https://classroom.github.com/classrooms/47958496-mdekmol 作為雲端教室, 目前已經利用亂數隨機分組, 建立 https://classroom.github.com/classrooms/47958496-mdekmol/group-assignments/cd2019a-task1, 將全班分為六組, 以便進行協同產品設計實習任務.

目前所推動的第一任務為可攜程式環境系統建置, 希望建立可隨身啟用的 Python, ANIS C 與 Lua 程式開發環境, 並內建 Solvespace, V-rep, Range3 以及其他常用工具, 例如: zoomit, processexplorer, kdiff3, sharex, HxD, wget 等.

參考資料

https://education.github.com/guide/assignments





Monday, February 25, 2019

多作者 Blogger

新增 Blogger 作者

根據 https://www.bloggingden.com/google-blogger-limitations/, 一個 Google blogger 最多只能邀
請 100 名協同作者.



進入 Blogger 中的

設定-基本-網誌作者

可以透過使用者電子郵箱, 邀請多人參與網誌著作, 可以邀請其他用戶擔任作者或管理員, 管理員可以管理網誌設定並編修所有網誌文章, 但是各作者只能編輯個人的文章.

四技一網際內容管理課程共用網誌: https://mde2019.blogspot.com/

五專一網際內容管理課程共用網誌: https://pmenfu.blogspot.com/

同時新增多名作者

在 Google Blogger 新增作者時, 需要在新增作者欄位填入電子郵箱, 若要同時邀請一個以上的作者, 則在各 email address 後面, 加上逗點隔開.

以下為處理上述兩個課程共用網誌作者時, 讀進修課人員名單, 然後附加 @gm.nfu.edu.tw 後, 同時新增 39 與 51 名作者.

filename = "wcm1aw1_student_list.txt"
with open(filename) as f:
    # 讀進全部檔案, 轉進數列, 並同時將各行的 \n 去除
    read_data = f.read().splitlines() 
for i in range(len(read_data)):
    print(str(read_data[i]) + "@gm.nfu.edu.tw, ")

四技一第一週修課人員名單: https://raw.githubusercontent.com/mdecourse/wcm2019/gh-pages/data/wcm1a/wcm1aw1_student_list.txt

五專一第一週修課人員名單: https://raw.githubusercontent.com/mdecourse/wcm2019/gh-pages/data/wcmj1a/wcmj1aw1_student_list.txt

Friday, February 22, 2019

Solvespace Binary STL export to ASCII STL

Solvespace 零件檔案轉出 Binary STL 格式, 利用下列 Python 程式轉為 Binary STL 格式後, 轉進 Range3 系統中.


import struct
normals = []
points = []
triangles = []
triangle_number = 0
def load_binary_stl(fp):
    '''
    二位元 STL 檔案格式如下:
    檔案標頭共有 80 個字元(bytes), 內容通常省略, 但是內容不可使用 solid, 以免與文字檔案 STL 混淆
    UINT8[80] – Header
    UINT32 – Number of triangles (I:佔 4 bytes 的 unsigned integer)
 
    foreach triangle
    REAL32[3] – Normal vector (f:每一座標分量為一佔 4 bytes 的 float, 共佔 12 bytes)
    REAL32[3] – Vertex 1
    REAL32[3] – Vertex 2
    REAL32[3] – Vertex 3
    UINT16 – Attribute byte count (H:兩個 bytes 的 unsigned short, 表示 attribute byte count)
    end
 
    '''
    # 已經在外部開檔
    #fp=open(filename,'rb')
    header=fp.read(80)
    triangle_number = struct.unpack('I',fp.read(4))[0]
    #print(triangle_number)
    count=0
    while True:
        try:
            p=fp.read(12)
            if len(p)==12:
                n=[struct.unpack('f',p[0:4])[0],struct.unpack('f',p[4:8])[0],struct.unpack('f',p[8:12])[0]]
                normals.append(n)
                l = len(points)
                #print(n)
            p=fp.read(12)
            if len(p)==12:
                p1=[struct.unpack('f',p[0:4])[0],struct.unpack('f',p[4:8])[0],struct.unpack('f',p[8:12])[0]]
                points.append(p1)
                #print(p1)
            p=fp.read(12)
            if len(p)==12:
                p2=[struct.unpack('f',p[0:4])[0],struct.unpack('f',p[4:8])[0],struct.unpack('f',p[8:12])[0]]
                points.append(p2)
            p=fp.read(12)
            if len(p)==12:
                p3=[struct.unpack('f',p[0:4])[0],struct.unpack('f',p[4:8])[0],struct.unpack('f',p[8:12])[0]]
                points.append(p3)
                triangles.append((l, l+1, l+2))
            # 使用 count 來計算三角形平面個數
            # triangle_number 為 STL 檔案中的三角形個數
            count += 1
            #print(count)
            # 在前面 12*4 個 bytes 的 normal 與三個點資料後, 為
            # 一個 2 bytes 長的 unsigned short, 其值為零, 為 attribute
            fp.read(2)
            # 讀完所有三角平面後, 即跳出 while
            if count > triangle_number:
                break
        except EOFError:
            break
    #fp.close()
def read_length(f):
    length = struct.unpack("@i", f.read(4))
    return length[0]
def read_header(f):
    f.seek(f.tell()+80)
def write_as_ascii(outfilename):
    f = open(outfilename, "w")
    f.write ("solid "+outfilename+"\n")
    for n  in range(len(triangles)):
        f.write ("facet normal {} {} {}\n".format(normals[n][0],normals[n][1],normals[n][2]))
        f.write ("outer loop\n")
        f.write ("vertex {} {} {}\n".format(points[triangles[n][0]][0],points[triangles[n][0]][1],points[triangles[n][0]][2]))
        f.write ("vertex {} {} {}\n".format(points[triangles[n][1]][0],points[triangles[n][1]][1],points[triangles[n][1]][2]))
        f.write ("vertex {} {} {}\n".format(points[triangles[n][2]][0],points[triangles[n][2]][1],points[triangles[n][2]][2]))
        f.write ("endloop\n")
        f.write ("endfacet\n")
    f.write ("endsolid "+outfilename+"\n")
    f.close()
def main():
    infilename = "binary1.stl"
    outfilename = "ascii1.stl"
    try:
        f = open(infilename, "rb")
        #read_header(f)
        #l = read_length(f)
        try:
            load_binary_stl(f)
            l = len(normals)
        except Exception as e:
            print("Exception",e)
        print(len(normals), len(points), len(triangles), l)
        write_as_ascii(outfilename)
        print("done")
    except Exception as e:
        print(e)
if __name__ == '__main__':
    main()

其他相關程式:

建立 Binary STL 零件檔案:

#coding: utf-8
import struct
 
class StLFacet:
    def __init__(self, normal, v1, v2, v3, att_bc=0):
        self.coords = [normal, v1, v2, v3]
        self.att_bc = att_bc
 
class StL:
    def __init__(self, header):
        self.header = header
        self.facets = []
    def add_facet(self, facet):
        self.facets.append(facet)
    def get_binary(self):
        # 原先 2.0 的版本
        #out = ['%-80.80s' % self.header]
        # 改為 Python 3.0 格式
        # 第一行標頭的格式
        header = ['%-80.80s' % self.header][0]
        # 利用 bytes() 將標頭字串轉為二位元資料
        out = [bytes(header,encoding="utf-8")]
        # 接著則計算三角形面的數量, 並以二位元長整數格式存檔
        out.append(struct.pack('L',len(self.facets)))
        # 接著則依照法線向量與三個點座標的格式, 分別以浮點數格式進行資料附加
        for f in self.facets:
            for coord in f.coords:
                out.append(struct.pack('3f', *coord))
            # att_bc 則內定為 0
            out.append(struct.pack('H', f.att_bc))
        return b"".join(out)
 
def test():
    stl=StL('Header ...')
    stl.add_facet(StLFacet((0.,0.,1.),(0.,0.,0.),(1.,0.,0.),(0.,1.,0.)))
    stl.add_facet(StLFacet((0.,0.,1.),(1.,0.,0.),(1.,1.,0.),(0.,1.,0.)))
    # 第二個平面
    stl.add_facet(StLFacet((0.,-1.,0.),(0.,0.,0.),(0.,0.,-1.),(1.,0.,-1.)))
    stl.add_facet(StLFacet((0.,-1.,0.),(0.,0.,0.),(1.,0.,-1.),(1.,0.,0.)))
    return stl.get_binary()
 
# 指定存為 binary 格式檔案
stlfile = open("test.stl", "wb")
stlcontent = test()
stlfile.write(stlcontent)

PyGame STL viewer:
#coding: utf-8
# STL viewer 原始檔案來自
# University of Wuppertal - http://mbi-wiki.uni-wuppertal.de/wordpress/
# Modified by Uli Eggersmann
# Binary STL 資料讀取原始作者 Oliver Marks - http://www.linux.com
# 原始檔案僅讀取 Text STL 零件檔案
# 2011 Fall 由 KMOL 新增 Binary STL 零件檔案讀取
 
from visual import scene, color, materials, faces, points
import os, struct
 
#file ="ritzel.stl"
 
file ="binary.stl"
 
scene.width = 400
scene.height = 400
scene.background = color.white # black
# 視窗標題取自 cvisual.pyd, 不可使用中文
scene.title = "STLViewer in VPython"
 
print ("利用滑鼠右鍵旋轉")
print ("滑鼠左右鍵同時按下後移動, 可以縮放畫面")
 
# Read STL file, only use vertex-line with xyz coordinates
list = []
 
#load stl file detects if the file is a text file or binary file
def load_stl(filename):
    #read start of file to determine if its a binay stl file or a ascii stl file
    fp=open(filename,'rb')
    header=fp.read(80)
    filetype=header[0:5]
    # 這裡必須要能夠分辨二位元字串與文字字串
    #print (type(filetype))
    #print (filetype)
    fp.close()
 
    # for Python 3
    if filetype==b'solid':
    # for Python 2
    #if filetype=='solid':
        print ("讀取文字檔案格式:"+str(filename))
        load_text_stl(filename)
    else:
        print ("讀取二位元檔案格式:"+str(filename,))
        load_binary_stl(filename)
 
#load binary stl file check wikipedia for the binary layout of the file
#we use the struct library to read in and convert binary data into a format we can use
def load_binary_stl(filename):
    '''
    二位元 STL 檔案格式如下:
    檔案標頭共有 80 個字元(bytes), 內容通常省略, 但是內容不可使用 solid, 以免與文字檔案 STL 混淆
    UINT8[80] – Header
    UINT32 – Number of triangles (I:佔 4 bytes 的 unsigned integer)
 
    foreach triangle
    REAL32[3] – Normal vector (f:每一座標分量為一佔 4 bytes 的 float, 共佔 12 bytes)
    REAL32[3] – Vertex 1
    REAL32[3] – Vertex 2
    REAL32[3] – Vertex 3
    UINT16 – Attribute byte count (H:兩個 bytes 的 unsigned short, 表示 attribute byte count)
    end
 
    '''
    global list
 
    fp=open(filename,'rb')
    header=fp.read(80)
 
    triangle_number = struct.unpack('I',fp.read(4))[0]
    count=0
    while True:
        try:
            p=fp.read(12)
            if len(p)==12:
                n=[struct.unpack('f',p[0:4])[0],struct.unpack('f',p[4:8])[0],struct.unpack('f',p[8:12])[0]]
 
            p=fp.read(12)
            if len(p)==12:
                p1=[struct.unpack('f',p[0:4])[0],struct.unpack('f',p[4:8])[0],struct.unpack('f',p[8:12])[0]]
                list.append(p1)
            p=fp.read(12)
            if len(p)==12:
                p2=[struct.unpack('f',p[0:4])[0],struct.unpack('f',p[4:8])[0],struct.unpack('f',p[8:12])[0]]
                list.append(p2)
            p=fp.read(12)
            if len(p)==12:
                p3=[struct.unpack('f',p[0:4])[0],struct.unpack('f',p[4:8])[0],struct.unpack('f',p[8:12])[0]]
                list.append(p3)
            # 使用 count 來計算三角形平面個數
            # triangle_number 為 STL 檔案中的三角形個數
            count += 1
            # 在前面 12*4 個 bytes 的 normal 與三個點資料後, 為
            # 一個 2 bytes 長的 unsigned short, 其值為零, 為 attribute
            fp.read(2)
            # 讀完所有三角平面後, 即跳出 while
            if count > triangle_number:
                break
        except EOFError:
            break
    fp.close()
 
 
def load_text_stl(filename):
    global list
    for dataline in open(filename,"r").readlines():
        if not dataline.strip(): # skip blank lines
            continue
        field = dataline.split() # split with no argument makes the right place!
        if field[0] == "vertex":
            list.append([float(x) for x in field[1:4]])
            #print (list)
            #break
            #for x in field[1:4]:
                #print(x)
 
load_stl(os.path.abspath('')+'/'+file)
 
 
# Graphics
model = faces(pos=list, color=(0.8,0.8,0.8),
              material=materials.plastic) # creates triangles
# 請注意, 這裡並沒有使用 STL 檔案中的平面 normal, 而是利用 VPython make_normals() 產生
model.make_normals() # creates plane normals
model.smooth(0.93) # smooths the edges
# = AllepunkteSTL points (pos = list, size = 3, color = Color.Black) # generates points 

PyGame 與 OpenGL 檢視 STL
#coding: utf8
# source: https://www.linux.com/community/blogs/133-general-linux/291889
import os
import struct
 
from OpenGL.GL import *
from OpenGL.GLU import *
import pygame
from pygame.locals import *
 
#class for a 3d point
class createpoint:
    def __init__(self,p,c=(1,0,0)):
        self.point_size=0.5
        self.color=c
        self.x=p[0]
        self.y=p[1]
        self.z=p[2]
 
    def glvertex(self):
        glVertex3f(self.x,self.y,self.z)
 
#class for a 3d face on a model
class createtriangle:
    points=None
    normal=None
 
    def __init__(self,p1,p2,p3,n=None):
        #3 points of the triangle
        self.points=createpoint(p1),createpoint(p2),createpoint(p3)
 
        #triangles normal
        self.normal=createpoint(self.calculate_normal(self.points[0],self.points[1],self.points[2]))#(0,1,0)#
 
    #calculate vector / edge
    def calculate_vector(self,p1,p2):
        return -p1.x+p2.x,-p1.y+p2.y,-p1.z+p2.z
 
    def calculate_normal(self,p1,p2,p3):
        a=self.calculate_vector(p3,p2)
        b=self.calculate_vector(p3,p1)
        #calculate the cross product returns a vector
        return self.cross_product(a,b)    
 
    def cross_product(self,p1,p2):
        return (p1[1]*p2[2]-p2[1]*p1[2]) , (p1[2]*p2[0])-(p2[2]*p1[0]) , (p1[0]*p2[1])-(p2[0]*p1[1])
 
class loader:
    model=[]
 
    #return the faces of the triangles
    def get_triangles(self):
        if self.model:
            for face in self.model:
                yield face
 
    #draw the models faces
    def draw(self):
        glBegin(GL_TRIANGLES)
        for tri in self.get_triangles():
            glNormal3f(tri.normal.x,tri.normal.y,tri.normal.z)
            glVertex3f(tri.points[0].x,tri.points[0].y,tri.points[0].z)
            glVertex3f(tri.points[1].x,tri.points[1].y,tri.points[1].z)
            glVertex3f(tri.points[2].x,tri.points[2].y,tri.points[2].z)
        glEnd()
 
    #load stl file detects if the file is a text file or binary file
    def load_stl(self,filename):
        #read start of file to determine if its a binay stl file or a ascii stl file
        fp=open(filename,'rb')
        h=fp.read(80)
        type=h[0:5]
        fp.close()
 
        if type=='solid':
            print ("reading text file"+str(filename))
            self.load_text_stl(filename)
        else:
            print ("reading binary stl file "+str(filename,))
            self.load_binary_stl(filename)
 
    #read text stl match keywords to grab the points to build the model
    def load_text_stl(self,filename):
        fp=open(filename,'r')
 
        for line in fp.readlines():
            words=line.split()
            if len(words)>0:
                if words[0]=='solid':
                    self.name=words[1]
 
                if words[0]=='facet':
                    center=[0.0,0.0,0.0]
                    triangle=[]
                    normal=(eval(words[2]),eval(words[3]),eval(words[4]))
 
                if words[0]=='vertex':
                    triangle.append((eval(words[1]),eval(words[2]),eval(words[3])))
 
 
                if words[0]=='endloop':
                    #make sure we got the correct number of values before storing
                    if len(triangle)==3:
                        self.model.append(createtriangle(triangle[0],triangle[1],triangle[2],normal))
        fp.close()
 
    #load binary stl file check wikipedia for the binary layout of the file
    #we use the struct library to read in and convert binary data into a format we can use
    def load_binary_stl(self,filename):
        fp=open(filename,'rb')
        h=fp.read(80)
 
        l=struct.unpack('I',fp.read(4))[0]
        count=0
        while True:
            try:
                p=fp.read(12)
                if len(p)==12:
                    n=struct.unpack('f',p[0:4])[0],struct.unpack('f',p[4:8])[0],struct.unpack('f',p[8:12])[0]
 
                p=fp.read(12)
                if len(p)==12:
                    p1=struct.unpack('f',p[0:4])[0],struct.unpack('f',p[4:8])[0],struct.unpack('f',p[8:12])[0]
 
                p=fp.read(12)
                if len(p)==12:
                    p2=struct.unpack('f',p[0:4])[0],struct.unpack('f',p[4:8])[0],struct.unpack('f',p[8:12])[0]
 
                p=fp.read(12)
                if len(p)==12:
                    p3=struct.unpack('f',p[0:4])[0],struct.unpack('f',p[4:8])[0],struct.unpack('f',p[8:12])[0]
 
                new_tri=(n,p1,p2,p3)
 
                if len(new_tri)==4:
                    tri=createtriangle(p1,p2,p3,n)
                    self.model.append(tri)
                count+=1
                fp.read(2)
 
                if len(p)==0:
                    break
            except EOFError:
                break
        fp.close()
 
 
 
class draw_scene:
    def __init__(self,style=1):
        #create a model instance and
        self.model1=loader()
        #self.model1.load_stl(os.path.abspath('')+'/text.stl')
        self.model1.load_stl(os.path.abspath('')+'/cube.stl')
        self.init_shading()
 
 
    #solid model with a light / shading
    def init_shading(self):
        glShadeModel(GL_SMOOTH)
        glClearColor(0.0, 0.0, 0.0, 0.0)
        glClearDepth(1.0)
        glEnable(GL_DEPTH_TEST)
        glShadeModel(GL_SMOOTH) 
        glDepthFunc(GL_LEQUAL)
        glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST)
 
        glEnable(GL_COLOR_MATERIAL)
        glEnable(GL_LIGHTING)
        glEnable(GL_LIGHT0)   
        glLight(GL_LIGHT0, GL_POSITION,  (0, 1, 1, 0))      
        glMatrixMode(GL_MODELVIEW)
 
    def resize(self, width, height):
        if height==0:
            height=1
        glViewport(0, 0, width, height)
        glMatrixMode(GL_PROJECTION)
        glLoadIdentity()
        gluPerspective(45, 1.0*width/height, 0.1, 100.0)
        #gluLookAt(0.0,0.0,45.0,0,0,0,0,40.0,0)
        glMatrixMode(GL_MODELVIEW)
        glLoadIdentity()
 
 
    def init(self):
        glShadeModel(GL_SMOOTH)
        glClearColor(0.0, 0.0, 0.0, 0.0)
        glClearDepth(1.0)
        glEnable(GL_DEPTH_TEST)
        glShadeModel(GL_SMOOTH) 
        glDepthFunc(GL_LEQUAL)
        glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST)
 
 
        glEnable(GL_COLOR_MATERIAL)
 
        glEnable(GL_LIGHTING)
        glEnable(GL_LIGHT0)   
        glLight(GL_LIGHT0, GL_POSITION,  (0, 1, 1, 0))
 
        glMatrixMode(GL_MODELVIEW)
 
    def draw(self):
        glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT)
        glLoadIdentity()
 
        glTranslatef(0.0,-26.0, -100.0)
        self.model1.draw()
 
#main program loop
def main():
    #initalize pygame
    pygame.init()
    pygame.display.set_mode((640,480), OPENGL|DOUBLEBUF)
 
    #setup the open gl scene
    scene=draw_scene()
    scene.resize(640,480)
 
    frames = 0
    ticks = pygame.time.get_ticks()
    while 1:
        event = pygame.event.poll()
        if event.type == QUIT or (event.type == KEYDOWN and event.key == K_ESCAPE):
            break
 
        #draw the scene
        scene.draw()
        pygame.display.flip()
        frames = frames+1
 
    print ("fps:  %d" % ((frames*1000)/(pygame.time.get_ticks()-ticks)))
 
if __name__ == '__main__':
    main()

另一個 STL Writer:
#coding:utf-8
# source: http://code.activestate.com/recipes/578246-stl-writer/
 
import struct
 
ASCII_FACET = """facet normal 0 0 0
outer loop
vertex {face[0][0]:.4f} {face[0][1]:.4f} {face[0][2]:.4f}
vertex {face[1][0]:.4f} {face[1][1]:.4f} {face[1][2]:.4f}
vertex {face[2][0]:.4f} {face[2][1]:.4f} {face[2][2]:.4f}
endloop
endfacet
"""
 
BINARY_HEADER ="80sI"
BINARY_FACET = "12fH"
 
class ASCII_STL_Writer:
    """ Export 3D objects build of 3 or 4 vertices as ASCII STL file.
    """
    def __init__(self, stream):
        self.fp = stream
        self._write_header()
 
    def _write_header(self):
        self.fp.write("solid python\n")
 
    def close(self):
        self.fp.write("endsolid python\n")
 
    def _write(self, face):
        self.fp.write(ASCII_FACET.format(face=face))
 
    def _split(self, face):
        p1, p2, p3, p4 = face
        return (p1, p2, p3), (p3, p4, p1)
 
    def add_face(self, face):
        """ Add one face with 3 or 4 vertices. """
        if len(face) == 4:
            face1, face2 = self._split(face)
            self._write(face1)
            self._write(face2)
        elif len(face) == 3:
            self._write(face)
        else:
            raise ValueError('only 3 or 4 vertices for each face')
 
    def add_faces(self, faces):
        """ Add many faces. """
        for face in faces:
            self.add_face(face)
 
class Binary_STL_Writer(ASCII_STL_Writer):
    """ Export 3D objects build of 3 or 4 vertices as binary STL file.
    """
    def __init__(self, stream):
        self.counter = 0
        super(Binary_STL_Writer, self).__init__(stream)
 
    def close(self):
        self._write_header()
 
    def _write_header(self):
        self.fp.seek(0)
        self.fp.write(struct.pack(BINARY_HEADER, b'Python Binary STL Writer', self.counter))
 
    def _write(self, face):
        self.counter += 1
        data = [
            0., 0., 0.,
            face[0][0], face[0][1], face[0][2],
            face[1][0], face[1][1], face[1][2],
            face[2][0], face[2][1], face[2][2],
            0
        ]
        self.fp.write(struct.pack(BINARY_FACET, *data))
 
 
def example():
    def get_cube():
        # cube corner points
        s = 3.
        p1 = (0, 0, 0)
        p2 = (0, 0, s)
        p3 = (0, s, 0)
        p4 = (0, s, s)
        p5 = (s, 0, 0)
        p6 = (s, 0, s)
        p7 = (s, s, 0)
        p8 = (s, s, s)
 
        # define the 6 cube faces
        # faces just lists of 3 or 4 vertices
        return [
            [p1, p5, p7, p3],
            [p1, p5, p6, p2],
            [p5, p7, p8, p6],
            [p7, p8, p4, p3],
            [p1, p3, p4, p2],
            [p2, p6, p8, p4],
        ]
    '''
    for writing ASCII STL cube file
    with open('cube_ascii.stl', 'w') as fp:
        writer = ASCII_STL_Writer(fp)
        writer.add_faces(get_cube())
        writer.close()
    '''
    with open('cube_bin.stl', 'wb') as fp:
        writer = Binary_STL_Writer(fp)
        writer.add_faces(get_cube())
        writer.close()
 
if __name__ == '__main__':
    example()

建立 Windows 10 64 位元可攜程式系統




安裝複製與移除 Python 3.7.2 

從 https://www.python.org/ 官方網站下載 Python 3.7.2 Windows 64 位元安裝檔案, 即 Windows x86-64 executable installer

選擇安裝到 C:\p37 後, 將 p37 目錄複製到 C:\tmp\cd2019\data 目錄中.

之後將系統中的 C:\p37 以解除安裝移除. 解除安裝可以使用 開始 -> 設定 -> 應用程式 選擇將 Python 相關安裝系統移除. 或以 搜尋->control 帶出舊版的控制台後, 以 程式集 -> 解除安裝程式 移除所安裝的 Python 相關套件.

修改 pip.exe 中的 c:\p37\python.exe 設定

由於透過系統安裝的 C:\p37\Scripts\pip.exe 將 python.exe 所在目錄直接寫入二位元檔案中, 必須使用 HxD: https://mh-nexus.de/en/hxd/ 編輯, 改為可攜設定中的 y:\p37\python.exe

基本啟動與關閉批次檔案

為了讓每次隨身碟插入後, 取得任何磁碟代號都不影響可攜隨身系統的啟動設定, 準備以 subst 指令, 將存放可攜檔案的 data 目錄與 y 槽代號對應. 希望了解 subst 指令的用法, 可以在命令提示字元區輸入 subst /?

基本啟動批次檔案除了利用 @echo off 關閉指令執行時的回應外, 主要是設定磁碟代號與 data 目錄對應, 然後宣告 Python 所在路徑, 並且利用 path 設定指令搜尋路徑, 其中包含 y:\;y:\p37;y:\p37\Scripts 等, 表示隨後的指令將會放在所設定的目錄路徑中.

以 SciTE 作為文字與程式編輯器

從 https://www.scintilla.org/SciTE.html 可以下載最新版的 SciTE 編輯器, 下載解開壓縮即可執行.

在此, SciTE 除了可以編輯文字檔案與程式外, 也可設定用來執行 Python, ANSI C 與 Lua 程式.

start.bat

@echo off
set Disk=y
subst %Disk%: "data"

%Disk%:

set HomePath=%Disk%:\home
set HomeDrive=%Disk%:\home
set Home=%Disk%:\home

set PYTHONPATH=%Disk%:\p37\DLLs;%Disk%:\p37\Lib;%Disk%:\p37\Lib\site-packages;
set PYTHONHOME=%Disk%:\p37

path=%path%;%Disk%:;%Disk%:\p37;%Disk%:\p37\Scripts;%Disk%:\tcc;

start /MIN %Disk%:\wscite\SciTE.exe
start /MIN %Disk%:\wscite\SciTE.exe

start /MIN cmd.exe
start /MIN cmd.exe

Exit


stop.bat
@echo off
set Disk=y
path=%PATH%;
REM 終止虛擬硬碟與目錄的對應
subst %Disk%: /D

taskkill /IM SciTE.exe /F
REM 關閉 cmd 指令視窗
taskkill /IM cmd.exe /F
EXIT


SciTEGlobal.properties

# Unicode
code.page=65001


cpp.properties

ccopts=-pedantic -Os
#cc=g++ $(ccopts) -c $(FileNameExt) -o $(FileName).o
#ccc=gcc $(ccopts) -c $(FileNameExt) -o $(FileName).o
cc=y:\tcc\tcc.exe -run
ccc=y:\tcc\tcc.exe -run

make.command=make
command.compile.*.c=$(ccc) -std=c99
command.build.*.c=$(make.command)
command.build.*.h=$(make.command)
command.clean.*.c=$(make.command) clean
command.clean.*.h=$(make.command) clean
command.go.*.c=$(cc) $(FileNameExt)
#command.go.*.c=./$(FileName)


python.properties

if PLAT_WIN
 command.go.*.py=y:\p37\pythonw -u "$(FileNameExt)"


參考資料

https://www.toptal.com/c/after-all-these-years-the-world-is-still-powered-by-c-programming

cdb w1 所完成的版本.7z

cdb w4 版本.7z +  nodejs_with_ungit.7z, (ungit 設定說明)

cdb_w5 版本.7z (470 MB, 解開後 1.3 GB, wcm 與 cd 課程共用版本)

Fossil SCM 簡介, Fossil SCM 編譯, , 

Wednesday, February 20, 2019

國立五專 P-TECH 上課感想

Pathways in Technology Early College High school (技術導向早期學院高中) 是源自美國紐約公立高中與州立科技大學合作下, 以資訊科技為導向, 讓九年級學生提早到科技大學上課, 安排以三到四年的高中及兩年的科技大學規劃, 讓學生有機會直接進入 IT 公司服務.

2018 年台灣首度讓早期的三大國立工專重啟少量招生的所謂 P-TECH 式教學班, 但同時也有七個私立科技大學, 也趁機恢復部分科系的五專招生.

其實台灣近年各校在碩士班以上的學制就學並不踴躍的情況下, 將招生段往前移, 當然是各校維持定量招生的不錯策略, 但是此舉將會局部減少學生進入高工職就讀的學生數量, 或許在各國立高工學校的抗議下, 因此在 2019 學年度, 所謂恢復國立五專榮景的口號, 仍然停留在 2018 年的招生額度, 以虎尾科技大學為例, 只有單班精密機械設計工程科.

讓國中學生或高中學生, 有機會到普通大學或科技大學選課, 是個不錯的構想, 但是問題很多, 由於 P-TECH 在紐約, 以 IT 為導向教學時, 沒有本國語及外國語的教材問題, 國中甚至高中學生可以直接修習與電腦程式或網路相關的課程, 面對以英文打字及英文程式或教材的內容, 在閱讀及運用上, 問題可能較小, 但是在台灣, 直接讓高一學生修原本放在大一的電腦或程式相關課程, 若內容牽涉英文資料, 可能就必須在招生入學時, 將國文、英文及數學列為考試科目, 以便讓進入技術導向早期學院高中的高一生, 能有較為一致的學能, 有效在中英文夾雜的情況下推動專業科目.

其次, 以國立工專所恢復的少量招生名額而言, 花五年時間只能取得副學士的學歷, 進入職場後面對數量龐大的大學以上學歷競爭者, 是否能夠如期望融入職場, 或有機會繼續升學?

以過去國立工專時代, 五專及二技學制齊備的情況而言, 從高工職進入四技或從五專進入二技的管道暢通, 招生流程有考試篩選, 入學學生程度比較一致, 許多選擇升學的技職生, 可以與普通高中生一樣, 殊途同歸, 取得碩士以上學歷, 充分發揮個人的特長, 可以在就業或升學的選項上, 毫無罣礙, 但是這種小批量試辦五專學制招生, 目前所吸引的學生都是學能狀態優良的年輕學子, 一旦走完五年學制, 卻可能只剩下就業或少量名額插大的局部選項.

總之, 當我們在搖旗吶喊, 高舉恢復國立五專招生榮景口號的同時, 更應該要負起責任, 為這批勇敢接受試煉的優秀學子, 打通更大的求學生涯管道, 不僅要在既有的五專科系招生上, 逐年增班或順勢恢復國立二技科系, 或者設法變出 5+3 的碩士升學選項.

其他參考資料

五專優先免試入學辦理現況與建議.pdf

五專恢復招生弊多於利-以機械科系為例.pdf

Monday, February 18, 2019

亂數分組參考程式

"程式"中的"程"表示流程, 而"式"則指採用特定格式, 因此"程式"可以解釋為利用特定格式的語法來表達特定流程.

至於電腦程式 (或計算機程式), 則特指該流程是利用電腦執行, 而所謂的特定格式則指採用特定的計算機語言, 與本網誌相關的課程, 主要採用 Python, 其次還使用 Javascript, C/C++ 與 Lua 等程式語言.

學習計算機程式, 特定語言的語法與解題流程互為因果, 因此在過程中, 除了必須了解各程式語言的基本範式及語法外, 還需要針對解題流程中所使用的資料結構與演算法進行取捨, 對於中型以上程式, 還需選擇採用何種資料庫系統.

希望利用 Python 對各班學員亂數分組, 必須先了解如何讀進 (withopenread) 各班的學員檔案, 設法將各學員的學號轉進數列 (包含利用 splitlines() , 然後導入 random, 並且了解 random.shuffle() 的用法.

接著在編寫學員亂數分組程式時, 則可按照下列流程配置上述各程式元件:

  1. 讀進學員資料, 設法將各學員學號轉入數列
  2. 利用亂數模組中的 shuffle 函式, 隨機弄亂學員學號數列
  3. 利用重複迴圈, 從學員學號數列中一一取出, 準備進行分組
  4. 在迴圈中利用整數相除後取得餘數的規律, 依照每組幾人的規劃進行分組
  5. 在分組過程, 再設法利用數列的 append, 將各組學員學號納為分組數列
  6. 最後將各組數列再放入選課班級數列中備用

本學期各班配合協同執行課程實習任務, 進行學員亂數分組, 以下為參考程式:

import random
filename = 'cdaw1_student_list.txt'
# 每組人數
num_in_one_group = 8
# 組序由 1 開始
group = 1
# 各班分組後所得數列
c2019 = []
with open(filename) as f:
    # 讀進全部檔案, 轉進數列, 並同時將各行的 \n 去除
    read_data = f.read().splitlines() 
print("共有 " + str(len(read_data)) + " 位學員")
# 利用 shuffle 將數列隨機弄亂
random.shuffle(read_data)
for i in range(len(read_data)):
    # 利用整數相除的餘數進行分組
    if i%num_in_one_group == 0:
        # 列出分隔符號
        print("-"*20)
        print("group " + str(group) +":")
        # 在分組區隔時重置各組學員數列
        group_list = []
        print()
        # 同時列出與分隔標註對應 i 的數列內容
        print(read_data[i])
        group_list.append(read_data[i])
        group = group + 1
    else:
        # 逐一列出同組的其他學員
        print(read_data[i])
        group_list.append(read_data[i])
    if i%num_in_one_group == 0:
        c2019.append(group_list)
# c2019 為該班分組後所得分組數列
print(c2019)

註: 第一週 cda 學員資料: cdaw1_student_list.txt

另外, 本學期除學員自評外, 加入各組學員互評機制, 請各組實際根據課程評量表單取回之資料, 以程式進行處理, 並提出此一評量機制可能帶來的問題與因應方案.

已知問題

利用 mde At nfu At edu At tw  G suite 建立自評與互評表單, 目前無法讓 gm At nfu At edu At tw 的用戶登入填表, 第一種解決方法為, 設法配合課程, 在 gm At nfu At edu At tw 網域帳號中重新建立表格, 第二種解決方案則是, 為每一學員建立 mde At nfu At edu At tw 網域下的臨時課程帳號 (課程結束後刪除).

ungit 使用操作示範

ungit 是本學期重新啟用的倉儲管理工具, 因為在 kmol_2019.7z 中並未納入, 因此使用者配置 kmol_2019 可攜系統之後, 必須再下載 nodejs_with_ungit.7z, 然後再設法配置, 直到能夠用 kungit.bat 啟動.

在今天上課時所錄製的簡單 ungit 操作影片:




至於在 Windows 10 環境中, 可攜 Python 3.7.2 系統的製作過程.

  1. 下載 Windows 10 64 位元 Python 3.7.2 安裝檔案.
  2. 進行安裝, 將 Python 裝在 c:\p37
  3. 安裝後將 p37 目錄取下, 放入隨身碟中 data 目錄中.
  4. 設定 start.bat 與 stop.bat
  5. 移除安裝在操作系統中的 Python 3.7.2
  6. 利用 https://mh-nexus.de/en/hxd/ 編輯與 pip 可執行有關的檔案, 修改啟動路徑.
  7. 測試是否可以正常開啟與關閉可攜 Python 系統

Wednesday, February 13, 2019

2019 Spring 課程目標

網際內容管理

讓學員得以利用全球資訊網發表與 (精密) 機械 (設計) 相關的內容, 並進行有效管理.

內容發表: Google Blogger, Github Pages

有效管理: 透過 API 程式整合兩者之間的內容, 包括 CMSimfly, Reveal 與 Pelican Blog 與 Blogger

協同產品設計實習

讓學員得以利用 Onshape 以協同模式, 進行機電資有關的產品開發, 並利用 V-rep 執行該產品的模擬驗證.

協同使用 Onshape

熟悉 V-rep 相關功能與應用

共同目標

各學員必須每週進行學習自評與互評, 並學習如何透過內容充實 (使用夠好麥克風錄音) 的影片 (on Youtube) 發表心得.


UChicago Leading Change



其他相關影片:






利用 Google 表單進行課程自評與互評

網際內容管理課程自評與互評表單: https://goo.gl/forms/gExeWw3JIrenXi453 (只允許 gm.nfu.edu.tw 帳號登入者填表)

協同產品設計實習課程自評與互評表單: https://goo.gl/forms/qtk5fxdeGYHCeI1M2 (只允許 gm.nfu.edu.tw 帳號登入者填表)

使用流程:

各學員在每週課程結束後至下週上課前時段, 可以利用上列表單回覆自評與互評結果.

由於各表單包含兩班修課人員, 因此將表單與 Google 試算表連結後, 可以將內如複製至 Ethercalc 查看, 或透過各班修課人員名單, 以程式從各週擷取各班學員自評與互評相關資料後, 以作為課程期中與期末評量時參考.

請注意: 本示範網誌以 disk1 AT Goodkym 達康登錄, 而上述評量表單則以 send1 At mde dot nfu dot edu dot tw 建立.



分組網誌回傳表單: https://goo.gl/forms/L5rlblsswi7nAz4v1 (由各班各分組組長負責填寫)

Tuesday, February 12, 2019

Simple Neural Network in Python

透過下列連結, 可以執行簡單的 Python 類神經網路程式:

https://medium.com/technology-invention-and-more/how-to-build-a-simple-neural-network-in-9-lines-of-python-code-cc8f23647ca1

from numpy import exp, array, random, dot
training_set_inputs = array([[0, 0, 1], [1, 1, 1], [1, 0, 1], [0, 1, 1]])
training_set_outputs = array([[0, 1, 1, 0]]).T
random.seed(1)
synaptic_weights = 2 * random.random((3, 1)) - 1
for iteration in range(1000):
    output = 1 / (1 + exp(-(dot(training_set_inputs, synaptic_weights))))
    synaptic_weights += dot(training_set_inputs.T, (training_set_outputs - output) * output * (1 - output))
print (1 / (1 + exp(-(dot(array([1, 0, 0]), synaptic_weights)))))
而此篇的另外一個重點為: 如何在 Google Blogger 中納入程式碼高亮顯示, 並且列出程式行號. 黨使用者登入 Google Blogger 後, View Blog 之右上方將會出現 Design 連結, 網誌管理者點選後, 可以進入 Theme, 並且選擇 Edit HTML, 將下列 Javascript 放在 head 標註中.

<script src='http://mde.tw/wcm2019/static/syntaxhighlighter/shCore.js' type='text/javascript'/>
<script src='http://mde.tw/wcm2019/static/syntaxhighlighter/shBrushCpp.js' type='text/javascript'/>
<script src='http://mde.tw/wcm2019/static/syntaxhighlighter/shBrushCSharp.js' type='text/javascript'/>
<script src='http://mde.tw/wcm2019/static/syntaxhighlighter/shBrushCss.js' type='text/javascript'/>
<script src='http://mde.tw/wcm2019/static/syntaxhighlighter/shBrushDelphi.js' type='text/javascript'/>
<script src='http://mde.tw/wcm2019/static/syntaxhighlighter/shBrushJava.js' type='text/javascript'/>
<script src='http://mde.tw/wcm2019/static/syntaxhighlighter/shBrushJScript.js' type='text/javascript'/>
<script src='http://mde.tw/wcm2019/static/syntaxhighlighter/shBrushPhp.js' type='text/javascript'/>
<script src='http://mde.tw/wcm2019/static/syntaxhighlighter/shBrushPython.js' type='text/javascript'/>
<script src='http://mde.tw/wcm2019/static/syntaxhighlighter/shBrushRuby.js' type='text/javascript'/>
<script src='http://mde.tw/wcm2019/static/syntaxhighlighter/shBrushSql.js' type='text/javascript'/>
<script src='http://mde.tw/wcm2019/static/syntaxhighlighter/shBrushVb.js' type='text/javascript'/>
<script src='http://mde.tw/wcm2019/static/syntaxhighlighter/shBrushXml.js' type='text/javascript'/>
<link href='http://mde.tw/wcm2019/static/syntaxhighlighter/css/shCoreDefault.css' rel='stylesheet' type='text/css'/>
<script type='text/javascript'>
SyntaxHighlighter.defaults['toolbar'] = false;
SyntaxHighlighter.all();
</script>

之後就可以透過 http://alexgorbatchev.com/SyntaxHighlighter/ 中的說明, 以 <pre class="brush: py"></pre> 或 <pre class="brush: js"></pre> 顯示對應的程式碼.

Monday, February 11, 2019

MIT China Summit

How We Will Learn: A 21st Century Vision of the Future of Education New Visions of Education and Research for the Benefit of Humankind The Quest for Intelligence The Quantum Computing: Present and Future How MIT thinks about natural and artificial intelligence

Rolex Submariner Watchmaking Demonstration




Google Blogger API 應用

https://medium.com/@durgaswaroop/publish-articles-on-blogger-in-just-one-second-2ef45586901

https://github.com/raghur/easyblogger

https://github.com/googleapis/google-api-python-client/tree/master/samples/blogger

https://console.developers.google.com/apis/credentials

http://angryplay.blogspot.com/2016/02/blogger-cookie-google-blogger-api-v3.html

http://www.easonhsu.idv.tw/2017/01/use-blogger-python-api-to-publish-from.html

其他

https://missterhao.me/2019/01/05/python-google-sheet-crud2/

http://vito-note.blogspot.com/2015/04/aspnet-google-oauth2.html

Google Blogger 參考

https://developers.google.com/blogger/docs/3.0/getting_started

https://developers.google.com/apis-explorer/#p/blogger/v3/

https://bitbucket.org/petraszd/blogger-to-hugo

Your Song

Your Song

It's a little bit funny this feeling inside

I'm not one of those who can easily hide

I don't have much money but boy if I did

I'd buy a big house where we both could live

If I was a sculptor, but then again, no

Or a man who makes potions in a travelling show

I know it's not much but it's the best I can do

My gift is my song and this one's for you

And you can tell everybody this is your song

It may be quite simple but now that it's done

I hope you don't mind

I hope you don't mind that I put down in words

How wonderful life is while you're in the world

I sat on the roof and kicked off the moss

Well a few of the verses well they've got me quite cross

But the sun's been quite kind while I wrote this song

It's for people like you that keep it turned on

So excuse me forgetting but these things I do

You see I've forgotten if they're green or they're blue

Anyway the thing is what I really mean

Yours are the sweetest eyes I've ever seen

And you can tell everybody this is your song

It may be quite simple but now that it's done

I hope you don't mind

I hope you don't mind that I put down in words

How wonderful life is while you're in the world

I hope you don't mind

I hope you don't mind that I put down in words

How wonderful life is while you're in the world

Monday, February 4, 2019

台灣技職教育何去何從?

請大家看一下這篇精闢的文章: 重塑技職,提升技術人才, 這篇文章有幾個重點:
  1. 當年技職司成立時, 台灣技職教育尚無科技大學, 目前則存在普通技職與高等技職等兩條不同路徑.
  2. 以德國「雙軌制」為例, 針對想鑽研基礎技藝的學生, 應該鼓勵引導他們到高職, 好好學習一技之長, 畢業即就業. 對於想進一步深造的學生, 科大應該更全面地培養他們的能力;除了專業能力, 也應包含英文、數理等基礎學科能力, 這樣才能與實務專業相輔相成.
  3. 科大需要的語文及數理等知識基礎, 因為高職時期學習時數不足, 影響到未來的學習與發展. 高職生大多數直接升學的現象, 造成就業或升學都需花加倍的時間與資源來彌補.
  4. 科技大學與普通大學, 兩者之間其實不一定要「為了不同而不同」.
  5. 高職應該提升就業比例, 高教與科大招生合流、打造頂尖科大.

台灣需不需要自行發展幾何核心?

我們所謂的幾何核心 (Geometric Kernel), 類似 https://en.wikipedia.org/wiki/Russian_Geometric_Kernel, 是用來建立電腦輔助設計與分析套件的基本數學程式庫.

從資料中得知, 俄羅斯花了兩年的時間打造, 2013 年所完成的核心網站在: http://rgkernel.com/ 

當然, 受惠於法國, 目前 https://www.opencascade.com/ 有開源版本: https://github.com/tpaviot/oce, 在這種情況下, 台灣還需要自行開發類似的幾何核心套件嗎?

若從商業的角度來看, 大多公司在套件的開發上會採用 Parasolid 或 ACIS, 甚或像某些 MCAD 套件, 兩者兼用, 在商言商, 台灣應該也無需大張旗鼓, 自行開發商用核心.

但是, 若從國防應用的角度來看, 這可能是一條不得不走的路, 只是這項議題跟 KMOLab 完成沾不上邊, 就此打住, 只談教育界若希望擁有能夠自行掌握的幾何核心, 該如何下手.

在設法利用 MSYS2 編譯 OpenCASCADE 之前 (哼, 為何不用 MS 相關套件? 這確實是個好問題!!), 我們希望能夠充分掌握一個較小的開源核心, 也就是 https://github.com/solvespace/solvespace, 先取出其解題核心, 編譯成動態連結程式庫, 然後以 Python3 呼叫, 有關這點, 已經初步完成, 從 https://github.com/KmolYuan/Pyslvs-UI, 就可以找到這個 solver: https://github.com/KmolYuan/python-solvespace, 接下來的應用就是充分利用這個幾何解題程式庫, 發展與機構設計及合成有關的套件, 接著就是設法解析 Solvespace 的每一個層面, 能夠配合新技術, 延伸出自有的 MCAD 小型套件, 用來建構簡單的機械設計零組件.

一旦完成 Solvespace 的解析, 或許就能在 FreeCAD: https://www.freecadweb.org/ 的導引下, 朝更大範圍的核心開發與相關應用前進.


如何面對滿天飛的工業 4.0 與人工智能口號?

現在的許多報章雜誌, 幾乎滿滿的工業 4.0 與人工智能內容, 不知道大家還記不記得當年的五年五百億以及兩兆雙星, 也是訊息充斥媒體, 結果呢? 

當然, 將全球當紅的技術架構, 透過宣傳廣為人知, 絕對是件好事, 但若其結果只是讓許多人趕進熱潮, 完全錯失重點, 到頭來空忙一場, 可就得不償失.

大家先看看 http://mde.tw/cadp2018/content/最佳化設計.html, 裡所列出來的所謂"最佳化設計" 題目, 其實是利用計算機程式的蠻力運算以及些許的突變設計, 在已知模型運算優劣的情況下, 讓電腦的"演化"運算, 替設計者尋找可用的"最佳"設計.

其實這個例子中設計模型有最佳解析解, 可以透過數學的微分求得, 將其採演化運算求解, 可以讓使用者了解, 若設計者能夠透過數學模擬與運算, 在最佳化設計參數組合並不確定的情況下, 電腦的計算能力, 可以協助設計者找到逼近解析最佳的設計參數組合.

而這樣的所謂最佳化設計運算的前提, 是使用者能夠在基本的物理與數學架構下, 找到提供程式運算比較各階段運算優劣的模型.

意思是, 若設計者不具備推導能夠輸入電腦程式的所謂"適應方程式", 上面的演化程式與電腦運算能力再強, 都派不上用場.

所以初步的結論是, 任何希望在演化最佳化設計流程中掌握關鍵的設計者, 都必須熟悉其相關設計領域所需的物理與數學原理, 沒有捷徑.

好了, 那麼這個簡單的演化運算與人工智能有沒有關係? 有, 但是透過蠻力運算的過程, 真正的智能還是來自人的數學推導與程式編寫, 這種小兒科的應用, "人工"智能還是遠遠地落在天邊.

至於所謂的工業 4.0, 以 KMOLab 目前的程度, 距離更是遙遠, 面對這些口號, 也許只能先當作充耳不聞, 先好好看些影片, 多讀一些書之後再來說: http://mde.tw/cd2019/content/AI.html


KMOLab 的動態網誌

過去十年, 從收費 hosting, 自行架站, 使用 OpenShift 雲端網站, 建立過許多相關網誌, 現在則透過 Google 的 Blogger 的免費服務, 配合網際內容管理課程, 建立這個 KMOLab 僅存唯一的網誌.

在使用收費 hosting 與自行架站的時代, 採用的是  Wordpress, 在 OpenShift 階段, 則加入 Pelican 靜態 Blog 與 Wordpress 整合, 好處是 Pelican Blog 可以同步使用 Github 倉儲, 各資料與系統的修改都具備版本, 可以完整儲存整體資料.

現在使用 Google Blogger, 若結合 Github Pages 上的 Pelican Blog 與 Google Blogger API 之間的整合, 應該也能達到類似的效果, 其中將圖片與大檔案存在 Google Drive, 影片則存在 Youtube, 這樣的使用, 似乎是現階段最佳的使用模式, 只是程式架構只有概念, 可能要利用 2019 年開課時段設法開發出來.

傳統的收費 hosting 不是不能使用, 而是架構不夠彈性, 假如沒有早期 OpenShift 的使用架構 (目前的 Heroku 使用流程類似), 感覺都無法永續, 只能將已經投入的軟硬體做最後的殘餘價值回收使用.

上述指的較為永續的雲端 hosting 使用架構, 最重要的就是能夠可攜的 client 端, 先前的 OpenShift 採用 ruby 打造, 早期的 Heroku 也是使用 ruby, 目前則以 node.js 打造, 使用起來還算方便, 只是 persistent data 的使用必須另尋途徑, 目前則僅將 Heroku 當作另一個展示動態程式用的 Github 倉儲使用, 還未涉入 persistent data 的結合.

總之, 假如要將時間再拉回 20 年前的 Zope, 提起網際內容管理系統的許多失敗流程, 就又是非常令人吁噓的一段經歷, 好的產品是有其生命歷程的, 想要永續, 就必須在供需上取得平衡, 免費使用 Google Blogger 的用戶, 若想要建立永續的使用, 除了參與 Adsense 的架構之外, 別無他途.

接下來就要開始啟動 Google 的廣告服務了.

以下測試能否導入 Facebook 影片:

(source: TerryGou and https://developers.facebook.com/docs/plugins/embedded-video-player/ )
不知為何, 上面的影片嵌入, 在 Chrome 沒有問題, 但是在 Firefox 卻無法正常顯示?

NX12

NX 12 Help https://docs.plm.automation.siemens.com/tdoc/nx/12/nx_help Python related https://docs.plm.automation.siemens.com/tdoc/nx/...